Dockerfile リファレンス

Docker は Dockerfile から命令を読み込み、自動的にイメージをビルドできます。 Dockerfile はテキストファイルであり、イメージを作り上げるために実行するコマンドライン命令を、すべてこのファイルに含められます。 docker build を実行すると、順次コマンドライン命令を自動化した処理を行い、ビルド結果となるイメージが得られます。

このページでは、 Dockerfile で利用可能なコマンドを説明します。このページを読み終えたら、 Dockerfileベストプラクティス を開き、理解を重視するガイドをご覧ください。

使用法

docker build コマンドは、 Dockerfileコンテキスト(context) からイメージを 構築(build) (ビルド)します。構築におけるコンテキストとは、指定された PATH (場所)または URL にあるファイル一式です。 PATH はローカル・ファイルシステム上のディレクトリです。 URL は Git リポジトリの場所です。

構築コンテキスト(build context) は再帰的に処理されます。つまり、 PATH にはすべてのサブディレクトリが含まれ、 URL にはリポジトリとサブモジュールが含まれます。以下の例における構築コマンドは、現在のディレクトリ( . )を構築コンテキストとして使用します。

$ docker build .

Sending build context to Docker daemon  6.51 MB
...

構築は Docker デーモンが実行するものであり、 CLI によるものではありません。構築処理でまず行われるのは、コンテキスト全体を(再帰的に)デーモンに送信します。たいていの場合、コンテキストとして空のディレクトリを用意し、そこに Dockerfile を置くのがベストです。そのディレクトリへは、Dockerfile の構築に必要なファイルのみ追加します。

警告

ルート・ディレクトリ / を構築コンテキストの PATH として指定しないでください。構築時、ハードディスクの内容すべてを Docker デーモンに転送してしまうからです。

構築コンテキスト内にあるファイルを使う場合、 COPY 命令など、 Dockerfile の命令で指定されたファイルを参照します。構築時の処理性能を上げるには、コンテキスト・ディレクトリに .dockerignore ファイルを追加し、不要なファイルやディレクトリを除外します。 .dockerignore ファイルを作成する詳細は、このページ内の ドキュメント を参照ください。

もともと、 DockerfileDockerfile と呼ばれ、コンテキストのルート(対象ディレクトリのトップ)に置かれました。 docker build-f フラグを使えば、Dockerfile がファイルシステム上のどこにあっても指定できます。

$ docker build -f /path/to/a/Dockerfile .

構築の成功時、新しいイメージを保存する リポジトリ(repository)タグ(tag) を指定できます。

$ docker build -t shykes/myapp .

構築後、複数のリポジトリに対してイメージをタグ付けするには、 biuld コマンドの実行時、複数の -t パラメータを追加します。

docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

Docker デーモンは Dockerfile 内に書かれた命令を実行する前に、事前に Dockerfile を検証し、構文が間違っている場合はエラーを返します。

$ docker build -t test/myapp .

[+] Building 0.3s (2/2) FINISHED
 => [internal] load build definition from Dockerfile                       0.1s
 => => transferring dockerfile: 60B                                        0.0s
 => [internal] load .dockerignore                                          0.1s
 => => transferring context: 2B                                            0.0s
error: failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition:
dockerfile parse error line 2: unknown instruction: RUNCMD

Docker デーモンは Dockerfile 内の命令を 1 つずつ実行し、必要な場合にはビルドイメージ内にその処理結果を 確定(commit) (コミット)し、最後に新しいイメージの ID を出力します。Docker デーモンは、送信されたコンテキスト内容を自動的に 除去(clean up) します。

各命令は個別に実行され、都度、新しいイメージが生成されますのでご注意ください。したがって、たとえば RUN cd /tmp という命令があっても、その次の命令には何ら影響を与えません。

Docker は可能な限り 構築キャッシュ(build-cache) を使用し、 docker build の処理を著しく高速にします。その場合はコンソール出力に CACHED というメッセージが出ます。(詳細については、 Dockerfile のベストプラクティスガイド を参照ください。)

$ docker build -t svendowideit/ambassador .

[+] Building 0.7s (6/6) FINISHED
 => [internal] load build definition from Dockerfile                       0.1s
 => => transferring dockerfile: 286B                                       0.0s
 => [internal] load .dockerignore                                          0.1s
 => => transferring context: 2B                                            0.0s
 => [internal] load metadata for docker.io/library/alpine:3.2              0.4s
 => CACHED [1/2] FROM docker.io/library/alpine:3.2@sha256:e9a2035f9d0d7ce  0.0s
 => CACHED [2/2] RUN apk add --no-cache socat                              0.0s
 => exporting to image                                                     0.0s
 => => exporting layers                                                    0.0s
 => => writing image sha256:1affb80ca37018ac12067fa2af38cc5bcc2a8f09963de  0.0s
 => => naming to docker.io/svendowideit/ambassador                         0.0s

構築キャッシュとは、デフォルトでは、構築するマシン上で以前に構築された結果に基づきます。 --cache-from オプションの指定により、イメージ・レジストリを通して配布された構築キャッシュも使えます。 docker build コマンドリファレンスの 外部のキャッシュをソースとして指定 セクションをご覧ください。

構築が終われば、 docker scanイメージを検査 したり、 Docker Hub にイメージを送信 したりできます。

BuildKit(ビルドキット)

バージョン 18.09 から、Docker は moby/buildkit プロジェクトによって提供された、新しい構築用バックエンドをサポートしています。古い実装に比べ、BuildKit バックエンドは多くの利点があります。たとえば、 BuildKit は次のことができます。

  • 使用していない 構築ステージ の検出とスキップ
  • 独立している構築ステージを 並列構築(parallelize building)
  • 構築コンテキストと構築の間では、変更のあったファイルのみ転送
  • 構築コンテキスト内で、未使用ファイルの検出と、転送のスキップ
  • 多くの新機能がある 拡張 Dockerfile 実装(external Dockerfile implementations) を使用
  • 他の API (中間イメージとコンテナ)による副作用を回避
  • 自動整理(automatic pruning) のために、構築キャッシュを優先度付け

BuildKit バックエンドを使うには、 docker build を実行する前に、CLI 上で環境変数 DOCKER_BUILDKIT=1 を設定する必要があります。

BuildKit を使った構築時に有効となる、拡張 Dockerfile 実装についての詳細を知るには、 BuildKit リポジトリにあるドキュメントを参照ください

書式

Dockerfile の書式は次の通りです。

# コメント
命令 引数

命令(instruction) は大文字と小文字を区別しません。ただし、引数と区別をつけやすくするため、慣例として引数は大文字です。

Docker は Dockerfile 内の命令を記述順に実行します。 Dockerfile は必ず FROM 命令で始めなければなりません。 ただし、 パーサ・ディレクティブコメント 、全体に適用される ARG の後になる場合があります。 FROM 命令で指定するのは、構築時に元となる 親イメージ です。 Dockerfile の中で、 FROM 行の 引数(arguments) として利用できる ARG 命令は、 FROM よりも前に記述できる唯一の命令です。

Docker は # で始まる行をコメントとして扱います。ただし、 パーサ・ディレクティブ は例外です。また、行の途中にある # は単なる引数として扱います。次のような記述ができます。

# コメント
RUN echo 'we are running some # of cool things'

Dockerfile で命令を実行する前に、コメント行は削除されます。つまり、以下の例にあるコメントは echo コマンドのシェル実行では扱われず、以下両方の例は同じものです。

RUN echo hello \
# コメント
world
RUN echo hello \
world

なお、コメント中では バックスラッシュ(line continuation characters) はサポートされていません。

注釈

空白についての注意

後方互換性のため、コメント( # )と( RUN のような)命令よりも前の空白を無視しますが、お勧めしません。以下のような例では、先頭の空白は保持されないため、どちらも同じものです。

        # これはコメント行です
   RUN echo hello
RUN echo world
# this is a comment-line
RUN echo hello
RUN echo world

ただし注意が必要なのは、以下の RUN 命令のように、命令に対する引数の空白は保持されます。そのため、以下の例では、先頭に空白が指定した通りある「 hello world」を表示します。

RUN echo "\
  hello\
  world"

パーサ・ディレクティブ(parser directives)

パーサ・ディレクティブ(parser directives) (構文命令)はオプションです。 Dockerfile で指定すると、以降の行での挙動に影響を与えます。パーサ・ディレクティブは構築時にレイヤを追加しないため、構築ステップで表示されません。パーサ・ディレクティブは # ディレクティブ(命令の名前)=値 という形式の、特別なタイプのコメントとして書きます。1つのディレクティブ(命令)は一度しか使えません。

コメントや空行、構築命令を処理すると、Docker はパーサ・ディレクティブを探さなくなります。そのかわり、パーサ・ディレクティブの記述はコメントとして扱われ、パーサ・ディレクティブかどうかは確認しません。そのため、すべてのパーサ・ディレクティブは Dockerfile の一番上に書く必要があります。

パーサ・ディレクティブは大文字と小文字を区別しません。しかし、小文字を使うのが慣例です。他にも慣例として、パーサ・ディレクティブの次行は空白にします。パーサ・ディレクティブ内では、 バックスラッシュ(line continuation characters) はサポートされません。

これらの規則があるため、次の例はどれも無効です。

(バックスラッシュを使った)行の継続はできません:

# ディレク \
ティブ=

(ディレクティブが)二度出現するため無効:

# ディレクティブ=値1
# ディレクティブ=値2

FROM イメージ名

構築命令の後に(パーサ・ディレクティブが)あっても、コメントとして扱う:

FROM ImageName
# ディレクティブ=値

コメントの後にパーサ・ディレクティブあれば、(単なる)コメントとして扱う:

# dockerfile についての説明
# ディレクティブ=値
FROM ImageName

不明なディレクティブは認識できないため、コメントとして扱う。さらに、パーサ・ディレクティブではないコメントの後にディレクティブがあっても、(命令としてではなく)コメントとして扱う:

# 不明な命令=値
# 正しい命令=値

パーサ・ディレクティブでは、改行ではない空白(スペース)を書けます。そのため、以下の各行はすべて同じように扱われます。 そこで、以下の各行はすべて同一のものとして扱われます。

#directive=value
# directive =value
#    directive= value
# directive = value
#      dIrEcTiVe=value

以下のパーサ・ディレクティブをサポートしています。

  • syntax
  • escape

syntax

# syntax=[リモート・イメージ・リファレンス]

例:

# syntax=docker/dockerfile:1
# syntax=docker.io/docker/dockerfile:1
# syntax=example.com/user/repo:tag@sha256:abcdef...

この機能は、 BuildKit バックエンドを利用時のみ使えます。そのため、古い構築バックエンドの利用時には、無視されます。

syntax ディレクティブ(命令)では、対象の Dockerfile が構築時に使う、 Dockerfile 構文(syntax) の場所を定義します。BuildKit バックエンドは Docker イメージとして配布され、コンテナのサンドボックス環境内で実行される 外部実装(external implementation) をシームレスに利用できます。

カスタム Dockerfile 実装により、次のことが可能になります。

  • Docker デーモンを更新しなくても、自動的にバグ修正をする
  • すべての利用者が確実に同じ実装を使い、Dockerfile で構築する
  • Docker デーモンを更新しなくても、最新機能を使う
  • Docker デーモンに統合前の、新機能やサードパーティ機能を試す
  • 他の build 定義や、自分自身で作成した定義 を使う

公式リリース

stable チャンネルは セマンティック・バージョニング に従います。たとえば、

  • docker/dockerfile:1 - 最新の 1.x.x マイナー および パッチ・リリースが更新され続ける
  • docker/dockerfile:1.2 - 最新の 1.2.x パッチ・リリースが更新され続けますが、 1.3.0 バージョンがリリースされると更新停止
  • docker/dockerfile:1.2.1 - 変わりません(immutable) :決して更新しない

私たちは docker/dockerfile:1 の使用を推奨します。これは、常にバージョン 1 構文(syntax) の最新 安定版(stable) リリースを示し、かつ、バージョン 1 のリリース・サイクルにおける「マイナー」と「パッチ」更新の両方も受け取れるからです。 BuildKit は構築の処理時、自動的に構文の更新を確認し、常に最新安定版を使うようにします。

1.21.2.1 のようなバージョンを指定すると、バグ修正や新機能を利用するには、 Dockerfile を手動で更新する必要があります。Dockerfile の古いバージョンは、 ビルダー(builder) の新しいバージョンと互換性を維持します。

labs チャンネル

「labs」チャンネルが提供するのは、まだ stable チャンネルでは利用できない、 Dockerfile機能に対する早期アクセスです。Labs チャンネル・イメージは stable リリースと連携しています。同じバージョンに -labs 文字が付きます。たとえば、

  • docker/dockerfile:labs - labs チャンネルの最新リリース
  • docker/dockerfile:1-labs - stable チャンネルの dockerfile:1 と同じで、labs 機能が有効化
  • docker/dockerfile:1.2-labs - stable チャンネルの dockerfile:1.2 と同じで、labs 機能が有効化
  • docker/dockerfile:1.2.1-labs - 変わりません(immutable) :決して更新しない。 stable チャンネルの dockerfile:1.2.1 と同じで、labs 機能が有効化

必要に応じて最適なチャンネルを選びます。新機能を活用したい場合は labs チャンネルを使います。labs チャンネルが提供するイメージは、stable チャンネルの 上位互換(superset) です。注意として、labs チャンネルのイメージでは、 stable 機能は セマンティック・バージョニング に従います。しかし「labs」機能は従いません。また、新しいリリースは下位互換性がない可能性もあるため、バージョンが固定されたフルバージョンでの指定をお勧めします。

「labs」機能、 マスター・ビルド(master builds)毎晩の機能リリース(nightly feature releases) に関するドキュメントは、 GitHub 上の BuildKit ソースリポジトリ にある説明をご覧ください。利用可能なイメージの一覧は、 Docker Hub のイメージ・リポジトリ や、開発ビルド用の docker/dockerfile-upstream image repositry をご覧ください。

escape

# escape=\ (バックスラッシュ)

または

# escape=` (バッククォート)

Dockerfile 内で文字を エスケープ(escape) するために使う文字を、 escape 命令で指定します。指定がなければ、デフォルトのエスケープ文字は \ です。

エスケープ文字は、行の中で文字をエスケープするのに使う場合と、改行をエスケープする(改行文字として使う)場合があります。これにより、 Dockerfile の命令は、複数の行に書けます。注意点としては、 Dockerfileescape パーサ・ディレクティブの有無にかかわらず、 RUN 命令ではエスケープされませんが、行末のみ(改行文字として)使用できます。

Windows 上では「 \ 」がディレクトリ・パスの区切り文字のため、エスケープ文字として「`」 を指定すると、とても使いやすいでしょう。 Windows PowerShell 上でも、「`」はエスケープ文字列として扱います。

以下にあるような、一見すると分かりづらい Windows 上での失敗例を考えます。2行目の行末にある、2つめの \ は、1つめの \ のエスケープ対象ではなく、改行(を表すエスケープ文字)として解釈されます。同じように、3行目の行末にある \ も、実際には命令として処理され、改行として扱われます。結果として、この Dockerfile では2行目と3行目は1つの命令と見なされます。

FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

結果:

PS E:\myproject> docker build -t cmd .

Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/2 : COPY testfile.txt c:\RUN dir c:
GetFileAttributesEx c:RUN: The system cannot find the file specified.
PS E:\myproject>

これを解決する方法の1つは、COPY 命令と dir の両方で、対象に対して / 文字を使うものです。これはベストですが、 Windows 上のパスとしては自然ではないため、混乱します。また、困ったことに、 Windows 上でコマンドすべてが / をパスの区切り文字としてサポートしておらず、エラーが発生しがちです。

次の Dockerfile は escape パーサ・ディレクティブの追加により、 Windows 上のファイルやパスを通常通りの構文として扱えるようになります(捕捉説明:デフォルトでは「 \ 」が改行文字として扱われています。あえてエスケープ文字を「`」と明示すると、特に「」がエスケープ文字かどうか考慮する必要がなくなり、パスの指定として「 C:\ 」の記述がそのまま扱えるようになります)。

# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

結果:

PS E:\myproject> docker build -t succeeds --no-cache=true .

Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/3 : COPY testfile.txt c:\
 ---> 96655de338de
Removing intermediate container 4db9acbb1682
Step 3/3 : RUN dir c:\
 ---> Running in a2c157f842f5
 Volume in drive C has no label.
 Volume Serial Number is 7E6D-E0F7

 Directory of c:\

10/05/2016  05:04 PM             1,894 License.txt
10/05/2016  02:22 PM    <DIR>          Program Files
10/05/2016  02:14 PM    <DIR>          Program Files (x86)
10/28/2016  11:18 AM                62 testfile.txt
10/28/2016  11:20 AM    <DIR>          Users
10/28/2016  11:20 AM    <DIR>          Windows
           2 File(s)          1,956 bytes
           4 Dir(s)  21,259,096,064 bytes free
 ---> 01c7f3bef04f
Removing intermediate container a2c157f842f5
Successfully built 01c7f3bef04f
PS E:\myproject>

環境変数の置き換え

ENV 命令 で宣言する ) 環境変数(environment variables) は、 Dockerfile で特定の命令に対する変数としての使用もできます。また、エスケープを使えば、変数のような構文も、命令文の文字列に入れられます。

Dockerfile 内での環境変数は、 $variable_name または ${variable_name} として書きます。どちらも同等に扱われます。中括弧( { } )を使う書き方は、 ${foo}_bar のように空白を使わず、変数名に割り当てる(ための変数)として通常は使います。

${variable_name} の書き方は、次のような(変数展開するための)標準的 bash 修飾子(modifiers) もサポートしています。

  • ${variable:-word}variable (「変数」として何らかの値)が設定されていれば、結果はその(変数が)値になる。 variable (変数)が指定されていなければ、 word が値として設定される。
  • ${variable:+word}variable (「変数」として何らかの値)が設定されていれば、結果は word が(変数の)値になり、それ以外の場合(変数の設定がない場合)は空の文字列になる。

どちらの例でも、(変数の中にある) word には任意の文字列を指定できますし、環境変数を追加した文字列も指定できます。

変数の前に \ を追加してエスケープできます。たとえば、 \$foo\${foo} は、 $foo${foo} という文字列に変換されます。

例( # の横が、変数展開した結果 ):

FROM busybox
ENV FOO=/bar
WORKDIR ${FOO}   # WORKDIR /bar
ADD . $FOO       # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux

環境変数は、 Dockerfile 内の以下の命令でサポートされています。

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD (以上の命令との組み合わせでサポートされます)

環境変数を置き換えでは、各命令(の行)全体を処理する間は、各変数は同じ値です。すなわち、この例では

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

結果、 def の値は hello であり、 bye ではありません。しかし、 ghi の値は bye です。なぜなら、(変数) abcbye を指定する命令の行と、この ghi の命令の行が違うからです。(捕捉説明: ENV abc=bye def=$abc の命令文の処理が終わるまでは $abchello のまま。この命令の処理が終わると、 $abcbye になる。そのため、次の命令分 ghi=$abc で、 $abc の値 bye が変数 ghi に入る )

.dockerignore ファイル

Docker CLI が コンテクスト(context)Dockerfile や Docker イメージの中に送りたいファイルなど、Docker イメージ構築時に必要な素材・内容物のこと)を docker デーモンに送信する前に、コンテクストのルート・ディレクトリで .dockerignore という名前のファイルを探します。このファイルが存在する場合、CLI はファイル内で書かれたパターンに一致するファイルやディレクトリを、コンテクストから除外します。これにより、容量が大きいか機微情報を含むファイルやディレクトリを、不用意にデーモンに送信するのを回避したり、 ADDCOPY を使ってイメージに加えてしまうのも回避したりするのに役立ちます。

CLI は .dockerignore ファイルを、改行で区切られたパターンの一覧として解釈します。パターンとは、Unix シェルの ファイル・グロブ(file glob) と似たものです。パターン一致にあたり、コンテクストのルートを 作業ディレクトリ(working directory) 、かつ、 ルート・ディレクトリ(root dhirectory) とみなします。たとえば、 /foo/barfoo/bar のパターンでは、どちらも PATH 、もしくは git リポジトリの場所を示す URL のルート以下で、 foo サブディレクトリ内の bar という名前のファイルかディレクトリを除外します。それ以外は除外しません。

.dockerignore ファイルでは、 # 記号で始まる行はコメントとみなされ、 CLI によって解釈される前に無視されます。

これは .dockerignore ファイルの例です:

# コメント
*/temp*
*/*/temp*
temp?

このファイルは構築時に以下の挙動をします。

ルール 挙動
# コメント 無視する。
*/temp* root 以下のあらゆるサブディレクトリ内で、 temp で始まる名前のファイルやディレクトリを除外。たとえば、テキストファイル /somedir/temporary.txt を除外し、同様にディレクトリ /somedir/temp も除外する。
*/*/temp* root から2階層以下のサブディレクトリ内で、 temp で始まる名前のファイルやディレクトリを除外。たとえば、 /somedir/subdir/temporary.txt を除外。
temp? ルートディレクトリ内で、 temp と1文字が一致する名前のファイルやディレクトリを除外。たとえば、 /tempa/tempb を除外。

一致には Go 言語の filepath.Match を使います。前処理として、前後の空白を削除し、... 要素を削除するのに Go 言語の filepath.Clean を使います。前処理により、空白になった行は無視されます。

Go 言語の filepath.Match ルールを拡張し、 Docker は特別なワイルドカード文字列 ** もサポートします。これは、(0も含む)複数のディレクトリに一致します。たとえば、 **/*.go.go で終わるすべてのファイルを除外します。つまり、構築コンテクストのルートも含む、全てのディレクトリが対象です。

! (エクスクラメーション・マーク)で始まる行は、除外対象の例外を指定します。次の .dockerignore ファイル例では、この仕組みを使っています:

*.md
!README.md

コンテクストから README.md例外として除き 、その他すべてのマークダウンファイルを除外します。

! による除外に対する例外ルールは、他の挙動にも影響します。 .dockerignore ファイルに書かれた最終行によって、特定のファイルが除外されるかどうかが決まるります。次の例を考えます:

*.md
!README*.md
README-secret.md

README-secret.md 以外の README ファイル( README*.md )は例外として除外されませんが、その他のマークダウンファイル( *.md )はコンテキストに含まれません。

次は、こちらの例を考えます:

*.md
README-secret.md
!README*.md

すべての README ファイルが含まれます(除外されません)。 !README*.mdREADME-secret.md と一致し、かつ最後の行にあるので、真ん中の行は無意味です。

.dockerignore ファイルを使い、 Dockerfile.dockerignore ファイルすら除外できます。除外設定をしても、これらのファイルは構築処理で必要なため、デーモンに送信されます。ですが、 ADDCOPY 命令で、これらのファイルをイメージにコピーしません。

ほかには、コンテクスト内に特定のファイルを除外するのではなく、入れたいファイルを指定したい場合もあるでしょう。そのためには、1つめのパターンとして * を指定し(一度、全てを除外する)、以降の行では ! で例外とするパターンを指定します。

注釈

これまでの経緯により、パターン . は無視されます。

FROM

FROM [--platform=<プラットフォーム>] <イメージ名> [AS <名前>]

または

FROM [--platform=<プラットフォーム>] <イメージ名>[:<タグ>] [AS <名前>]

または

FROM [--platform=<プラットフォーム>] <イメージ名>[@<ダイジェスト>] [AS <名前>]

FROM 命令は、新しい 構築ステージ(build stage) を初期化し、以降の命令で使う ベース・イメージ を指定します。そのため、正しい Dockerfile とは FROM 命令で始める必要があります。イメージとは、適切なイメージであれば何でも構いません。 公開リポジトリ から イメージを取得して始める のが、特に簡単です。

  • Dockerfile では、 FROM よりも前に書ける命令は ARG だけです。 ARG と FROM の相互作用を理解 をご覧ください。
  • 複数のイメージを作成する場合や、ある構築ステージを他からの依存関係として用いる場合のため、1つの Dockerfile `` に複数の ``FROM を書けます。新しい各 FROM 命令が処理される前には、その直前でコミットされた、最も新しいイメージ ID を単に表示するだけです。 FROM 命令があるたびに、それ以前の命令で作成されたあらゆる状態がクリアになります。
  • オプションとして、 FROM 命令に AS 名前 を追加し、新しい構築ステージに名前を付けられます。この名前は、以降の FROMCOPY --from=<名前> 命令で使用し、このステージで構築したイメージを参照できます。
  • タグダイジェスト 値はオプションです。どちらも省略すると、ビルダーは latest タグだとデフォルトで扱われます。 タグ 値(に相当するイメージ名)が見つからなければ、ビルダーはエラーを返します。

FROM でマルチプラットフォーム対応のイメージを参照する場合には、オプションの --platform フラグを使うと、特定のプラットフォーム向けイメージを指定できます。たとえば、 linux/amd64 や、 linux/arm64 や、 windows/amd64 です。デフォルトでは、今まさに利用しているプラットフォームを対象として構築します。 グローバル構築引数(global build arguments) が、このフラグの値をとして利用できます。たとえば 自動的なプラットフォーム ARG は、構築段階でネイティブな構築プラットフォームを上書きでき( --platform=$BUILDPLATFORM )、これを、そのステージ内で対象プラットフォーム向けのクロス・コンパイルとして利用できます。

ARG と FROM の相互作用を理解

1つめの FROM 命令の前に ARG 命令があり、そこで変数が宣言されていれば FROM 命令で参照できます。

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

FROM 命令より前に宣言された ARG は構築ステージ外のため、 FROM 命令以降で使えません。1つめの FROM よりも前に宣言された ARG のデフォルト値を使うには、構築ステージ内で値を持たない ARG 命令を使います。

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

RUN には2つの形式があります。

  • RUN <コマンド>シェル形式(shell form) 。コマンドはシェル内で実行される。デフォルトは Linux が /bin/sh -c で、 Windows は cmd /S /C
  • RUN ["実行ファイル", "パラメータ1", "パラメータ2"]実行形式(exec form)

RUN 命令は、現在のイメージよりも上にある新しいレイヤでコマンドを実行し、その結果を コミット(確定)(commit) します。結果が確定されたイメージは、 Dockerfile の次のステップで使われます。

RUN 命令の実行と、コミット処理によって生成される(イメージ・レイヤの)階層化とは、Docker の中心となる考え方に基づいています。これは、ソースコードを管理するかのように、手軽にコミットができ、イメージ履歴のどの場所からもコンテナを作成できます。

exec 形式は、シェル上の処理で文字列が改変されないようにします。加えて、シェルを実行するバイナリを含まないベース・イメージでも、 RUN 命令を実行できるようにします。

シェル形式で使うデフォルトのシェルは、 SHELL コマンドで変更できます。

シェル形式で \ (バックスラッシュ)を使うと、 RUN 命令を次の行に続けられます。たとえば、次の2行を考えます。

RUN /bin/bash -c 'source $HOME/.bashrc ;\
echo $HOME'

これは、次の1行に合わせたのと同じです。

RUN /bin/bash -c 'source $HOME/.bashrc ; echo $HOME'

/bin/sh 以外のシェルを使うには、 exec 形式で使いたいシェルを指定します。

RUN ["/bin/bash", "-c", "echo hello"]

注釈

exec 形式は JSON 配列(array) として構文解析されます。そのため、文字を囲むにはシングル・クォート(`)ではなく、ダブル・クォート( ")を使う必要があります。

シェル形式と異なり、 exec 形式はコマンドとしてのシェルを実行しません。つまり、通常のシェルとしての処理を行いません。たとえば、 RUN [  "echo", "$HOME" ] では、 $HOME を変数展開しません。もしも、シェルとしての総理を行いたければ、シェル形式を使うか、 RUN [ "sh", "-c", "echo $HOME" ] のように、直接シェルを実行します。 exec 形式もしくは直接シェルを実行する場合は、シェル形式と同じように処理をしているように見えますが、シェルが環境変数を処理しているのであり、 Docker が行っているのではありません。

注釈

(exec 形式の記述方法は JSON です)`JSON` 形式では、バックスラッシュをエスケープする必要があります。これが特に関係するのは、 パス区切り文字(path separator) にバックラッシュを使う Windows です。次の行は正しい JSON 形式ではないため、シェル形式として扱われます。しかし、想定していない動作を試みようとするため、処理は失敗します。

RUN ["c:\windows\system32\tasklist.exe"]

この例の正しい構文は、こちらです。

RUN ["c:\\windows\\system32\\tasklist.exe"]

RUN 命令(で処理された内容)のキャッシュは、次回以降の構築時にも、自動的に有効です。 RUN apt-get dist-upgrade -y のような命令に対するキャッシュは、次の構築時に再利用されます。 RUN 命令に対するキャッシュを無効にするには、 docker build --no-cache のように --no-cache フラグを使います。

詳細は、Dockerfile のベスト・プラクティス をご覧ください。

Dockerfile 中に ADD 命令と COPY 命令が出てくると、以降の RUN 命令の内容はキャッシュされません。

判明している問題 (RUN)

  • Issue 783 は、 AUFS ファイルシステム使用時に発生する、ファイルの権限(パーミッション)についての問題です。たとえば、ファイルを rm で削除するときに気づくかもしれません。

CMD

CMD 命令には3つの形式があります。

  • CMD ["実行ファイル","パラメータ1","パラメータ2"]exec 形式、こちらが望ましい )
  • CMD ["パラメータ1", "パラメータ2"]ENTRYPOINT 命令に対するデフォルトのパラメータとして扱う)
  • CMD コマンド パラメータ1 パラメータ2 (シェル形式)

CMD 命令は Dockerfile 中で1度しか使えません。複数の CMD 命令があれば、最後の CMD のみ有効です。

CMD の主な目的は、コンテナ実行時のデフォルト(初期設定)を指定するためです 。デフォルトには、実行ファイルを含める場合も、そうでない場合もあります。実行ファイルを含まない場合は、 ENTRYPOINT 命令の指定が必要です。

ENTRYPOINT 命令に対するデフォルトの引数を CMD` で指定する場合は、 CMD 命令と ENTRYPOINT 命令の両方を JSON 配列形式で指定する必要があります。

注釈

exec 形式は JSON 配列(array) として構文解析されます。そのため、文字を囲むにはシングル・クォート(`)ではなく、ダブル・クォート( ")を使う必要があります。

シェル形式と異なり、 exec 形式はコマンドとしてのシェルを実行しません。つまり、通常のシェルとしての処理を行いません。たとえば、 CMD [  "echo", "$HOME" ] では、 $HOME を変数展開しません。もしも、シェルとしての総理を行いたければ、シェル形式を使うか、 CMD [ "sh", "-c", "echo $HOME" ] のように、直接シェルを実行します。 exec 形式もしくは直接シェルを実行する場合は、シェル形式と同じように処理をしているように見えますが、シェルが環境変数を処理しているのであり、 Docker が行っているのではありません。

シェル形式か exec 形式の CMD 命令とは、対象イメージの起動時に処理するコマンドを指定します。

CMD をシェル形式にする場合、 /bin/sh -c の中で <コマンド> が実行されます。

FROM ubuntu
CMD echo "This is a test." | wc -

シェルを使わずに <コマンド> を実行 したければ、JSON 配列としてコマンドを記述する必要があり、その実行ファイルはフルパスで指定します。 この、JSON 配列形式が、CMD での望ましいフォーマットです 。パラメータを追加するには、その配列内で1つ1つの文字列として記述します。

FROM ubuntu
CMD ["/usr/bin/wc","--help"]

コンテナを起動するたびに、同じコマンドを毎回実行するのであれば、 ENTRYPOINT 命令と CMD 命令の組み合わせを検討ください。詳しくは をご覧ください。

docker run で引数を指定すると、 CMD で指定されているデフォルトの挙動を上書きできます。

注釈

RUNCMD を混同しないでください。 RUN は実際にコマンドを実行し、その結果をコミットします。対して、 CMD は構築時には何も実行しませんが、イメージを使って実行したいコマンドを指定するものです。

LABEL

LABEL <キー>=<値> <キー>=<値> <キー>=<値> ...

LABEL 命令は、イメージに メタデータ(metadata) を追加します。 LABELキー・バリュー(key-value) の組み合わせです。 LABEl の値に空白がある場合は、コマンドラインでの構文解析と同じように、引用符とバックラッシュを使います。いくつかの使用例がこちらです。

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

イメージは複数のラベルを持てます。1行で複数のラベルを指定できます。 Docker 1.10 未満では、この手法で最終イメージの容量を減らせましたが、今は違います。それでも1行で書く方法を選択するのであれば、2つの方法があります。

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

ベース・イメージか親イメージ( FROM 行にあるイメージ)を含むラベルは、イメージで継承されます。ラベルが既に存在していても、その値が違う場合は、直近で追加された値で、以前の値を上書きします。

イメージのラベルを表示するには、 docker image inspect コマンドを使います。 --format オプションを使えば、ラベルのみ表示できます。

$ docker image inspect --format='' myimage
"Labels": {
    "com.example.vendor": "ACME Incorporated"
    "com.example.label-with-value": "foo",
    "version": "1.0",
    "description": "This text illustrates that label-values can span multiple lines.",
    "multi.label1": "value1",
    "multi.label2": "value2",
    "other": "value3"
},

MAINTAINER(非推奨)

MAINTAINER <名前>

MAINTAINER 命令は、イメージを作成した Author (作成者)のフィールドを設定します。この命令よりも LABEL 命令のほうが、より柔軟であり、こちらを使うべきです。それにより、必要なメタデータの設定が簡単になり、 docker inspect などで簡単に表示できます。 MAINTAINER フィールドに相当するラベルは、次のように指定します。

LABEL maintainer="SvenDowideit@home.org.au"

こうしておけば、 docker inspect で他のラベルと一緒に表示できます。

EXPOSE

EXPOSE <ポート> [<ポート>/<プロトコル>...]

コンテナの実行時、指定した ネットワーク・ポート(network port) をコンテナがリッスンするように、Docker へ通知するのが EXPOSE 命令です。対象ポートが TCP か UDP か、どちらをリッスンするか指定できます。プロトコルの指定がなければ、 TCP がデフォルトです。

EXPOSE 命令だけは、実際にはポートを 公開(publish) しません。これは、どのポートを公開する意図なのかという、イメージの作者とコンテナ実行者の両者に対し、ある種のドキュメントとして機能します。コンテナの実行時に実際にポートを公開するには、 docker run-p フラグを使い、公開用のポートと割り当てる( マップ(map) する)ポートを指定します。

EXPOSE はデフォルトで TCP を前提としますが、 UDP も指定できます。

EXPOSE 80/udp

TCP と UDP の両方を公開するには、2行で書きます。

EXPOSE 80/tcp
EXPOSE 80/udp

この例で、 docker run-P オプションを付けると、TCP と UDP のそれぞれにポートを公開します。注意点としては、 -P を使うと、ホスト上で 一時的なハイポート(ephemeral high-ordered host port) を順番に使いますので、 TCP と UDP のポート番号が同じにならない場合もあります。

EXPOSE の設定に関係なく、実行時に -p フラグを使い、その設定を上書き出来ます。たとえば、次のようにします。

docker run -p 80:80/tcp -p 80:80/udp ...

ホストシステム上でポート転送を設定するには、 -P フラグの使い方 をご覧ください。 docker network コマンドはコンテナ間で通信するネットワークの作成をサポートしますが、特定のポートを露出したり公開したりを指定する必要はありません。これは、ネットワークに接続している複数のコンテナは、あらゆるポートを通して相互に通信できるからです。詳細な情報は、 この機能の上書き をご覧ください。

ENV

ENV <キー>=<値> ...

ENV 命令は、環境変数 <キー> に対し、値を <値> として設定します。この値は、以降に続く構築ステージ中で、環境変数として保持されます。その上、多くの場合、 その途中で置き換え 可能です。値は、他の環境変数を示すものとしても解釈できます。そのため、引用符はエスケープしなければ削除されます。コマンドラインでの構文解釈と同様に、引用符とバックラッシュによって、値のなかで空白を使えるようになります。

例:

ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy

ENV 命令では、一度に複数の <キー>=<値> ... 変数を指定できます。次の例は、先ほどの例の結果と(環境変数の値が)完全に同じになります。

ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
    MY_CAT=fluffy

ENV 命令を使い設定した環境変数は、結果として作成されたイメージから実行したコンテナでも維持されます。 docker inspect を使い、この値を確認できます。そして、それらを変更するには docker run --env <キー>=<値> を使います。

環境変数の維持は、予期しない悪影響を引き起こす可能性があります。たとえば、 ENV DEBIAN_FRONTEND=noninteractive` を設定すると、 ``apt-get の挙動を変えます。そのため、イメージの利用者を混乱させるかもしれません。

環境変数が構築中のみ必要で、最終イメージで不要な場合は、代わりにコマンドで値の指定を検討ください。

RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ...

あるいは、 ARG を使えば、最終イメージでは保持されません。

ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y ...

注釈

別の書き方

ENV 命令では、 ENV <キー> <値> のように、 = を省略する別の構文があります。例:

ENV MY_VAR my-value

この構文では、1つの ENV 命令で、複数の環境変数を設定できません。そのため、混乱を引き起こす可能性があります。たとえば、以下の指定では、1つの環境変数( ONE )に対し、値 "TWO= THREE=world" を設定します。

ENV ONE TWO= THREE=world

後方互換のため、この別の書き方がサポートされています。しかし、先述で説明した理由のため、使わないほうが良いでしょう。加えて、将来のリリースでは削除される可能性があります。

ADD

ADD には 2 つの形式があります。

ADD [--chown=<ユーザ>:<グループ>] <追加元>... <追加先>
ADD [--chown=<ユーザ>:<グループ>] ["<追加元>",... "<追加先>"]

パスに空白を含む場合には、後者の形式が必要です。

注釈

--chown 機能がサポートされているのは、 Linux コンテナを構築するために使う Dockerfile 上のみです。そのため、 Windows コンテナ上では機能しません。Linux と Windows 間では、ユーザとグループの所有者に関する概念を変換できません。ユーザ名とグループ名を ID に変換するには、 /etc/passwd/etc/group を使いますが、これができるのは Linux OS をベースとしたコンテナのみです。

ADD 命令では、追加したいファイル、ディレクトリ、リモートファイルの URL を <追加元> で指定すると、これらをイメージのファイルシステム上のパス <追加先> に追加します。

複数の 追加元 リソースを指定できます。ファイルやディレクトリの場合、それぞれのパスは、構築コンテキストの追加先に対する相対パスとして解釈されます。

それぞれの 追加元 には、Go 言語の filepath.Match ルールを使い、ワイルドカードや一致が処理されます。

"hom" で始まるファイルすべてを追加するには、次のようにします。

ADD hom* /mydir/

次の例では、 ? は "home.txt" のような1文字に置き換えられます。

ADD hom?.txt /mydir/

<追加先> は絶対パスか WORKDIR (作業ディレクトリ)からの相対パスであり、これらの(追加元の)ソースを送信先コンテナ内にコピーします。

以下は相対パスを使う例で、 "test.txt" を <WORKDIR>/relativeDir/ (相対ディレクトリ)に追加します。

ADD test.txt relativeDir/

次は絶対パスを使う例です。 /absoluteDir/ (相対ディレクトリ)に "test.txt" を追加します。

ADD test.txt /absoluteDir/

特殊文字( [] など)を含むファイルやディレクトリを追加する場合は、Go 言語のルールに従い、各パスをエスケープする必要があります。たとえば、ファイル名 arr[0].txt, を追加するには、次のようにします。

ADD arr[[]0].txt /mydir/

新しいファイルやディレクトリは、オプションの --chown フラグでユーザ名、グループ名、UID/GID の組み合わせて追加対象の権限指定リクエストを指定しない限り、 UID と GID が 0 として作成されます。 --chwon フラグの書式により、ユーザ名とグループ名の文字列の指定や、整数として直接 UID と GID のあらゆる組み合わせの指定ができます。ユーザ名かグループ名を指定すると、コンテナのルート・ファイルシステム上にある /etc/passwd/etc/group ファイルを使い、その名前から適切な整数の UID や GID にそれぞれ変換する処理が行われます。以下は --chown フラグを使って適切に定義する例です。

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

もしも、コンテナのルート・ファイルシステムに /etc/passwd/etc/group ファイルが無く、さらに --chown フラグで使われたユーザ名やグループ名が存在しない場合は、 ADD の処理段階で構築が失敗します。整数で ID を指定すると、(ユーザ名が存在しているかどうか)検索する必要がなく、コンテナのルート・ファイルシステムの内容に依存しません。

<追加元> がリモートにあるファイル URL の場合には、追加先のパーミッションは 600 になります。もしも、リモートファイルの取得時に HTTP ヘッダ Last-Modified があれば、追加先の mtime を設定するために使います。しかしながら、 ADD の途中で処理される他のファイルと同じように、ファイルが変更されたかどうかや、キャッシュを更新するかどうかを判断するために mtime は使われません。

注釈

標準入力(STDIN) を通し( docker build - < ファイル名Dockerfile を渡して構築する場合は、 構築コンテクスト(build context) が存在しませんので、 Dockerfile で URL をベースとした ADD のみ追加可能です。また、標準入力で圧縮したアーカイブも渡せます( docker build - < archive.tar.gz )ので、アーカイブのルートにある Dockerfile と、以降に続くアーカイブ内容が、構築用のコンテクストとして利用されます。

もしも URL ファイルが認証によって保護されている場合、ADD 命令は認証をサポートしていないため、コンテナ内で RUN wgetRUN curl など他のツールを使う必要があります。

注釈

ADD 命令を処理するにあたり、 <追加元> の内容が変更されている場合は、その Dockerfile の対象行以降でキャッシュを無効にします。RUN 命令のためのキャッシュも、無効になる対象です。詳しい情報は ベストプラクティス・ガイド - 構築キャッシュの活用 をご覧ください。

ADD は以下のルールに従います。

  • <追加元> のパスは、構築コンテクスト内にある必要があります。つまり、 ADD .../どこか /どこか のように指定できません。これは、 docker build の第一段階が、コンテクスト対象のディレクトリ(とサブディレクトリ)を docker デーモンに対して送信するからです。
  • もしも <追加元> が URL で 追加先 の最後が スラッシュ記号(trailing slash) で終わっていなければ、URL からファイルをダウンロードした後、 <追加先> にコピーします。
  • もしも <追加元> が URL で 追加先 の最後が スラッシュ記号(trailing slash) で終わっていれば、URL からファイル名を推測し、それから <追加先>/<ファイル名> にファイルをダウンロードします。たとえば、 ADD http://example.com/foobar / は、ファイル /foobar を作成します。その際、適切なファイル名を検出できるようするため、URL には何らかのパスを含める必要があります( http://example.com は動作しません )。
  • <追加元> がディレクトリであれば、ファイルシステムのメタデータも含む、ディレクトリの内容すべてがコピーされます。

注釈

対象ディレクトリそのものはコピーしません。ディレクトリの内容のみコピー対象です。

  • <追加元> がローカルにあり、認識できる圧縮形式( 無圧縮(identity) 、 gzip 、 gzip2、xz )の tar アーカイブの場合は、ディレクトリとして 展開(unpacked) します。リモート URL からのリソースは展開 しません 。ディレクトリのコピーまたは展開は、 tar -x の挙動と同じ、次の処理の組み合わせです。

    1. 送信先に何らかのパスが存在していたら、
    2. 1つ1つのファイルごとに、ソースツリーに含まれる方を優先して処理(コピー)する

    注釈

    ファイルが認識できる圧縮形式かどうかにかかわらず、ファイル名ではなく、対象ファイルの内容に基づいて処理が行われます。たとえば、空ファイルの名前が .tar.gz だとしておm、これは圧縮ファイルとは認識されず、ファイル展開に関するエラーメッセージは一切表示 されず 、それどころか、ファイルは単に追加先にコピーされます。

  • 追加元 が何らかのファイルの場合は、そのメタデータと一緒に個別にコピーされます。 <追加先>スラッシュ記号(trailing slash) で終わっている場合は、これがディレクトリとみなされ、 <追加元><追加先>/base(<送信元>) に書き込まれます。
  • 複数の <追加元>スラッシュ記号(trailing slash) で終わっていなければ、対象は通常のファイルとみなされ、 <追加元> の内容が、 <追加先> に書き込まれます。
  • <追加先> が存在しない場合は、対象パス内に存在していないディレクトリ全てと共に作成されます。

COPY

ADD には 2 つの形式があります。

COPY [--chown=<ユーザ>:<グループ>] <コピー元>... <コピー先>
COPY [--chown=<ユーザ>:<グループ>] ["<コピー元>",... "<コピー先>"]

パスに空白を含む場合には、後者の形式が必要です。

注釈

--chown 機能がサポートされているのは、 Linux コンテナを構築するために使う Dockerfile 上のみです。そのため、 Windows コンテナ上では機能しません。Linux と Windows 間では、ユーザとグループの所有者に関する概念を変換できません。ユーザ名とグループ名を ID に変換するには、 /etc/passwd/etc/group を使いますが、これができるのは Linux OS をベースとしたコンテナのみです。

COPY 命令では、追加したいファイル、ディレクトリを <コピー元> で指定すると、これらをイメージのファイルシステム上のパス <コピー先> に追加します。

複数の コピー元 リソースを指定できます。ファイルやディレクトリの場合、それぞれのパスは、構築コンテキストのコピー先に対する相対パスとして解釈されます。

それぞれの コピー元 には、Go 言語の filepath.Match ルールを使い、ワイルドカードや一致が処理されます。

"hom" で始まるファイルすべてを追加するには、次のようにします。

COPY hom* /mydir/

次の例では、 ? は "home.txt" のような1文字に置き換えられます。

COPY hom?.txt /mydir/

<コピー先> は絶対パスか WORKDIR (作業ディレクトリ)からの相対パスであり、これらの(コピー元の)ソースを送信先コンテナ内にコピーします。

以下は相対パスを使う例で、 "test.txt" を <WORKDIR>/relativeDir/ (相対ディレクトリ)に追加します。

COPY test.txt relativeDir/

次は絶対パスを使う例です。 /absoluteDir/ (相対ディレクトリ)に "test.txt" を追加します。

COPY test.txt /absoluteDir/

特殊文字( [] など)を含むファイルやディレクトリを追加する場合は、Go 言語のルールに従い、各パスをエスケープする必要があります。たとえば、ファイル名 arr[0].txt, を追加するには、次のようにします。

COPY arr[[]0].txt /mydir/

新しいファイルやディレクトリは、オプションの --chown フラグでユーザ名、グループ名、UID/GID の組み合わせて追加対象の権限指定リクエストを指定しない限り、 UID と GID が 0 として作成されます。 --chwon フラグの書式により、ユーザ名とグループ名の文字列の指定や、整数として直接 UID と GID のあらゆる組み合わせの指定ができます。ユーザ名かグループ名を指定すると、コンテナのルート・ファイルシステム上にある /etc/passwd/etc/group ファイルを使い、その名前から適切な整数の UID や GID にそれぞれ変換する処理が行われます。以下は --chown フラグを使って適切に定義する例です。

COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/

もしも、コンテナのルート・ファイルシステムに /etc/passwd/etc/group ファイルが無く、さらに --chown フラグで使われたユーザ名やグループ名が存在しない場合は、 COPY の処理段階で構築が失敗します。整数で ID を指定すると、(ユーザ名が存在しているかどうか)検索する必要がなく、コンテナのルート・ファイルシステムの内容に依存しません。

注釈

標準入力(STDIN) を通し( docker build - < ファイル名Dockerfile を渡して構築しようとしても、 構築コンテクスト(build context) が存在しませんので、 COPY は使えません。

オプションで、これまでの( FROM .. AS <名前> として作成した)構築ステージをコピー元(ソース)の場所として指定するために、 COPY--from=<名前> フラグを利用できます。これは、ユーザ自身が構築コンテキストを送る作業の替わりとなります。

COPY は以下のルールに従います。

  • <コピー元> のパスは、構築コンテクスト内にある必要があります。つまり、 COPY .../どこか /どこか のように指定できません。これは、 docker build の第一段階が、コンテクスト対象のディレクトリ(とサブディレクトリ)を docker デーモンに対して送信するからです。
  • <コピー元> がディレクトリであれば、ファイルシステムのメタデータも含む、ディレクトリの内容すべてがコピーされます。

注釈

対象ディレクトリそのものはコピーしません。ディレクトリの内容のみコピー対象です。

  • コピー元 が何らかのファイルの場合は、そのメタデータと一緒に個別にコピーされます。 <コピー先>スラッシュ記号(trailing slash) で終わっている場合は、これがディレクトリとみなされ、 <コピー元><コピー先>/base(<コピー元>) に書き込まれます。
  • 複数の <コピー元>スラッシュ記号(trailing slash) で終わっていなければ、対象は通常のファイルとみなされ、 <コピー元> の内容が、 <コピー先> に書き込まれます。
  • <コピー先> が存在しない場合は、対象パス内に存在していないディレクトリ全てと共に作成されます。

注釈

COPY 命令を処理するにあたり、 <コピー元> の内容が変更されている場合は、その Dockerfile の対象行以降でキャッシュを無効にします。RUN 命令のためのキャッシュも、無効になる対象です。詳しい情報は ベストプラクティス・ガイド - 構築キャッシュの活用 をご覧ください。

ENTRYPOINT

ENTRYPOINT には2つの形式があります。

exec 形式は、推奨されている形式です:

ENTRYPOINT ["実行ファイル", "パラメータ1", "パラメータ2"]

shell 形式:

ENTRYPOINT コマンド パラメータ1 パラメータ2

ENTRYPOINT は、コンテナを 実行ファイル(executable) として処理するように設定できます。

たとえば、以下はデフォルト設定の nginx が、ポート 80 をリッスンして起動します。

docker run -i -t --rm -p 80:80 nginx

コマンドラインでの docker run <イメージ名> に対する引数は、 exec 形式の ENTRYPOINT の全要素の後に追加されます。そして、 CMD を使って指定した全ての要素は上書きされます。これにより、 エントリーポイント(entry point) に対する引数として渡せます。たとえば、 docker run <イメージ> -d では、エントリーポイントに対して引数 -d を渡せます。また、 ENTRYPOINT 命令の上書きは、 docker run --entrypoint フラグを使います。

shell 形式では、 CMDrun コマンドラインの引数を使えません。 ENTRYPOINT/bin/sh -c のサブコマンドとして起動されるため、シグナルを渡せません。そのため、実行ファイルはコンテナの PID 1 ではなく、Unix シグナルを受信できません。つまり、 docker stop <コンテナ名> を実行しても、実行ファイルは SIGTERM シグナルを受信しません。

ENTRYPOINT を複数書いても、Dockerfile 中で一番最後の 命令しか処理されません。

exec 形式の ENTRYPOINT 例

ENTRYPOINTexec 形式は、確実に実行するデフォルトのコマンドと引数を設定するために使います。そして、 CMD のどちらかの形式を使い、変わる可能性があるデフォルト(のパラメータや引数)を指定します。

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

このコンテナを実行すると、唯一のプロセスとして top が見えます。

$ docker run -it --rm --name test  top -H

top - 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

さらに詳しく調べるには、 docker exec が使えます。

$ docker exec -it test ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux

top を適切に終了するには、docker stop test を実行します。

以下の Dockerfile は、Apache をフォアグラウンドで実行するために(つまり、 PID 1 として) ENTRYPOINT を使います。

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

実行ファイルの 起動スクリプト(starter script) を書く必要がある場合は、最後に起動する実行ファイルが Unix シグナルを確実に受信するようにするには、 execgosu コマンドを使えます。

#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

最後に、 停止時(shutdown) に追加のクリーンアップ(あるいは他のコンテナとの通信)をする場合や、複数の実行ファイルを組み合わせている場合は、 ENTRYPOINT スクリプトが Unix シグナルを受信し、続いて、他の処理を行うようにする必要があります。

#!/bin/sh
# メモ:sh でスクリプトを書いたため、buxybox コンテナも動作する

# サービスの停止後に、必要があれば手動でクリーンアップできるようにする場合や、
# 1つのコンテナ内で複数のサービスを起動できるようにする場合には、トラップを使う
trap "echo TRAPed signal" HUP INT QUIT TERM

# ここでは、バックグラウンドでサービスを起動
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# ここでは、サービスの停止とクリーンアップ
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"

このイメージを docker run -it --rm -p 80:80 --name test apache で実行すると、以後、 docker execdocker top でコンテナのプロセスを確認できます。その後、Apache を 停止(stop) するようにスクリプトへ求めします。

$ docker exec -it test ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux

$ docker top test

PID                 USER                COMMAND
10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054               root                /usr/sbin/apache2 -k start
10055               33                  /usr/sbin/apache2 -k start
10056               33                  /usr/sbin/apache2 -k start

$ /usr/bin/time docker stop test

test
real 0m 0.27s
user 0m 0.03s
sys  0m 0.03s

注釈

ENTRYPOINT 設定は --entrypoint を使って上書きできます。しかし、ここでの設定は、実行ファイルとしての 処理(exec) だけです( sh -c は使いません)。

注釈

exec 形式は JSON 配列として解釈されますので、単語を囲むにはシングルクオート(')ではなくダブルクオート(")を使う必要があります。

シェル 形式とは異なり、 exec 形式はコマンドシェルを呼び出しません。つまり、通常のシェルとしての処理が怒らないのを意味します。たとえば、 ENTRYPOINT [ "echo", "$HOME" ] では、 $HOME を変数展開しません。シェルとしての処理を行いたい場合には、 シェル 形式を使うか、 ENTRYPOINT [ "sh", "-c", "echo $HOME" ] のようにシェルを直接実行します。exec 形式を使って直接シェルを実行する場合は、シェル形式の場合と同様に、環境変数の展開をするのはシェルであり、 Docker ではありません。

シェル形式の ENTRYPOINT 例

単に文字列を ENTRYPOINT` で指定するだけで、 /bin/sh -c の中で実行できます。この形式では、環境変数を展開するためにシェルの処理を使います。そして、 CMDdocker run コマンドラインでの引数は無視されます。長期に実行している ENTRYPOINT の実行バイナリに対し、 docker stop で適切にシグナルを送るには、 exec で起動する必要があるのを念頭に置いてください。

FROM ubuntu
ENTRYPOINT exec top -b

このイメージで実行すると、 PID 1 のプロセスが1つ見えます。

$ docker run -it --rm --name test top

Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b

docker stop でクリーンに終了します。

$ /usr/bin/time docker stop test

test
real 0m 0.20s
user 0m 0.02s
sys  0m 0.04s

仮に、 ENTRYPOINT のはじめに exec を追加し忘れたとします。

FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1

そして、これを使って起動します(指定した名前は次のステップで使います)。

$ docker run -it --name test top --ignored-param2

Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
CPU:   9% usr   2% sys   0% nic  88% idle   0% io   0% irq   0% sirq
Load average: 0.01 0.02 0.05 2/101 7
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     S     3168   0%   0% /bin/sh -c top -b cmd cmd2
    7     1 root     R     3164   0%   0% top -b

top の出力から、 ENTRYPOINT の指定したものが PID 1 ではないのが分かります。

docker stop test を実行しても、コンテナはきれいに終了しません。 stop コマンドはタイムアウト後に SIGKILL を強制送信するからです。

$ docker exec -it test ps aux

PID   USER     COMMAND
    1 root     /bin/sh -c top -b cmd cmd2
    7 root     top -b
    8 root     ps aux

$ /usr/bin/time docker stop test

test
real 0m 10.19s
user 0m 0.04s
sys  0m 0.03s

CMD と ENTRYPOINT の連携を理解

CMDENTRYPOINT 命令は、どちらもコンテナ起動時に何のコマンドを実行するか定義します。命令を書くにあたり、両方が連携するには、いくつかのルールがあります。

  1. Dockerfile は、少なくとも1つの CMDENTRYPOINT 命令を書くべきです。
  2. ENTRYPOINT は、コンテナを 実行バイナリ(exeutable) のように扱いたい場合に定義すべきです。
  3. CMD は、 ENTRYPOINT コマンドに対するデフォルトの引数を定義するためか、、コンテナ内でその場その場でコマンドを実行するために使うべきです。
  4. CMD は、コンテナの実行時に、ほかの引数で上書きされる場合があります。

以下の表は、 ENTRYPOINTCMD の組み合わせで、どのような処理が行われるかを示します。

  ENTRYPOINT なし ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]
CMD なし エラー。実行できない。 /bin/sh -c exec_entry p1_entry exec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_cmd p1_cmd exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry p1_cmd p2_cmd exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

注釈

ベース・イメージで CMD が定義されている場合でも、 ENTRYPOINT の設定によって CMD は空の値にリセットされます。このような場合は、現在のイメージで何らかの値を持つように、 CMD を定義する必要があります。

VOLUME

VOLUME ["/data"]

VOLUME 命令は、指定した名前で マウントポイント(mount point) を作成します。そして、(Docker が動いている)自ホスト上や他のコンテナといった、外部からマウントされたボリュームを収容する場所として、そのマウントポイントが示します。ここでの値は VOLUME ["/var/log/"] のような JSON 配列か、 VOLUME /var/logVOLUME /var/log /var/db のような複数の引数を持つ単なる文字列です。 詳しい情報やサンプル、Docker クライアントを経由してマウントする方法は ボリュームを通したディレクトリ共有 を参照ください。

docker run 命令は、ベース・イメージ内で指定した場所に何かデータがあっても、そこを新規に作成するボリュームとして初期化します。例えば、次のような Dockerfile の抜粋で考えます。

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

この Dockerfile で docker run によって作成されるイメージとは、新しいマウントポイントを /myvol へ作成し、(その場所にあった) greeting ファイルを新規作成するボリュームへコピーします。

ボリューム指定についての注意

Dockerfile 内でのボリュームの扱いについては、以下の点に注意してください。

  • Windows ベースのコンテナ上のボリューム : Windows ベースのコンテナを利用する場合、コンテナ内でのボリューム指定先は、次のどちらかの必要があります。

    • 存在していないディレクトリ、または、空のディレクトリ
    • C ドライブ以外
  • Dockerfile 内からのボリューム変更 :ボリュームを宣言後、構築ステップでボリューム内のデータに対する変更があったとしても、それらの変更は破棄されます(反映されません)。

  • JSON 形式 :引数リストは JSON 配列として扱われます。文字を囲むにはシングル・クォート( ' )ではなくダブル・クォート( " )を使う必要があります。

  • コンテナ実行時に宣言されるホスト側ディレクトリホスト側ディレクトリ(host directory) (マウントポイント)は、その性質上、ホストに依存します。これはイメージの移植性を維持するためであり、指定対象のホスト側ディレクトリが、全てのホスト上で利用可能である保証がないためです。この理由により、Dockerfile 内からホスト側ディレクトリをマウントできません。 VOLUME 命令は、一切の `ホスト側ディレクトリ に対するパラメータ指定をサポートしません。(ホスト側を指定する必要がある場合は)コンテナの実行時や作成時に、マウントポイントを指定しなくてはいけません。

USER

USER <ユーザ>[:<グループ>]

または

USER <UID>[:<GID>]

注釈

ユーザに対してグループを指定する場合、そのユーザは指定したグループに「のみ」所属しますので注意してください。他のグループ所属の設定は無視されます。

警告

ユーザに対して所属グループの指定が無ければ、イメージ(または以降の命令)の実行時に root グループとして実行されます。 Windows では、まず、初期に作成済みではないユーザを作成が必要です。これをするには、 Dockerfile で net user コマンドを使う必要があります。

FROM microsoft/windowsservercore
# コンテナ内で Windows ユーザを作成
RUN net user /add patrick
# 次のコマンドを指定
USER patrick

WORKDIR

WORKDIR /path/to/workdir

WORKDIR 命令は、Dockerfile 内で以降に続く RUNCMDENTRYPOINTCOPYADD 命令の処理時に(コマンドを実行する場所として)使う 作業ディレクトリ(working directory) を指定します。 WORKDIR が存在していなければ作成されます。これは、以降の Dockerfile で使われなくてもです。

WORKDIR 命令は Dockerifle 内で何度も利用できます。相対パスを指定すると、その前の WORKDIR 命令で指定された場所に対する相対パスになります。

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

この Dockerfile で、最後の pwd コマンドの出力は /a/b/c になります。

WORKDIR 命令は、 ENV 命令で設定済みの環境変数を展開できます。利用できる環境変数は、 Dockerfile で明示したものだけです。例:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

この Dockerfile では、最後の pwd コマンドの出力は /path/$DIRNAME になります。

ARG

ARG <名前>[=<デフォルト値>]

ARG 命令は 構築時(build-time) にユーザが渡せる変数を定義します。変数を渡すには、構築時に docker biuld コマンドで --build-arg <変数名>=<値> を指定します。もしも、ユーザが構築時に引数を指定しても、 Dockerfile 中で指定が無ければ、構築時に警告が出ます。

[Warning] One or more build-args [foo] were not consumed.

([警告] build-arg [foo] は使われませんでした)

Dockerfile に1つまたは複数の ARG 命令を入れられます。たとえば、以下は正しい Dockerfile です。

FROM busybox
ARG user1
ARG buildno
# ...

警告

GitHub キーやユーザの認証情報のような 機微情報(シークレット)(secret) を、構築時に変数として使うのは推奨しません。これは、どのようなイメージも docker history コマンドを使えば、構築時の変数を表示できるからです。

イメージ構築時に機微情報(シークレット)を安全に使う方法を学ぶには、 BuildKit でイメージを構築 をご覧ください。

デフォルトの値

オプションで、ARG 命令にデフォルト値を指定できます。

FROM busybox
ARG user1=someuser
ARG buildno=1
...

もし ARG 命令にデフォルト値があり、構築時に値の指定が無ければ、ビルダーはデフォルト値を使います。

変数の範囲

ARG 変数の定義が有効になるのは、 Dockerfile で定義された後の行であり、コマンドライン上などでの引数ではありません。たとえば、このような Dockerfile を例に考えましょう。

FROM busybox USER ${user:-some_user} ARG user USER $user ...

このファイルを使って構築するには、次のコマンドを実行するとします。

$ docker build --build-arg user=what_user .

構築時、コマンドラインの引数で変数の値を指定していても、 ARG 命令で変数を定義するまでは、その変数の値は空白の文字列として扱われます。2行目の USER 命令は、この時点では変数 $user に対する値が定義されていないため、変数 $user の値は some_user として処理されます(シェルなどの変数展開と同じで、 ${user:-some_user} は、変数 $user の値が未定義であれば、 some_user を値に入れる処理です )。続く3行目の ARG 命令により、コマンドラインで指定した引数 what_user が、変数 $user に対する値として設定されます。そして、4行目の USER 命令で使われている $userwhat_user になります。

ARG 命令が有効な範囲は、構築ステージの定義が終わるまでです。複数のステージで 引数(arg) を使うには、ステージごとに ARG 命令が必要です。

FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

ARG で変数を使うには

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER v1.0.0
RUN echo $CONT_IMG_VER

そして、次のコマンドでイメージを作ったとしましょう。

$ docker build --build-arg CONT_IMG_VER=v2.0.1 Dockerfile

この場合、 RUN 命令で使われる値は、ユーザが(コマンドラインで)指定した ARG 命令の値 v2.0.1 ではなく v1.0.0 です。これは、シェルスクリプトの挙動に似ています。定義した時点から、 ローカルな範囲の変数(ローカルスコープ変数)(locally scoped variable) は、引数として指定した変数や、環境変数として継承された変数をで上書きします。

先ほどの例と違う ENV 仕様を使えば、 ARG 命令と ENV 命令の間で、より役立つ相互関係を作れます。

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER

ARG 命令とは違い、 ENV の値は常に構築イメージ内に保持されます。 --build-arg フラグがない docker build を考えます。

$ docker build .

この Dockerfile 例を使うと、 (docker build 時に引数の指定がなかったため)3行目の ENV 命令によってデフォルトの v1.0.0 が変数 CONT_IMG_VER の値となり、この値がイメージ内でずっと保持されます。

今回の例にある変数展開の手法によって、コマンドラインから引数を渡し、 ENV 命令を利用して最終的なイメージまで保持できます。なお、変数展開をサポートしているのは、 Dockerfile 命令の一部 のみです。

定義済みの ARG 変数

Docker には、Dockerfile 内で対応する ARG 命令を使わなくても利用できる 定義済み(predefined)ARG 変数があります。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy

これらの変数を使うには、コマンドライン上の --build-arg フラグで渡します。

$ docker build --build-arg CONT_IMG_VER=v2.0.1 .

デフォルトでは、これらの定義済み変数は docker history の出力から除外されます。それらを除外することで、 HTTP_PROXY 変数のような機微情報が漏洩する危険性を減らします。

たとえば、次の Dockerifle を使い 、 `` --build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com`` で構築する例を考えます。

FROM ubuntu
RUN echo "Hello World"

こうすると、 HTTP_PROXY 変数の値は docker history で見えなくなり、キャッシュもされません。もしも、プロキシサーバの場所が http://user:pass@proxy.sfo.example.com に変わったとしても、以後の構築で(誤った情報を)キャッシュすることによる構築失敗もありません。

このような挙動を上書きしたい場合は、次の Dockerfile のような ARG 命令を追加します。

FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"

この Dockerfile で構築すると、 HTTP_PROXYdocker history (で見えるように)保存され、この値を変更すると構築キャッシュは無効化されます。

グローバル範囲での自動的なプラットフォーム ARG 変数

この機能が利用できるのは、 BuildKit バックエンドを利用する時のみです。

構築を処理するノード( 構築プラットフォーム(build platform) )と、結果として作成されるイメージ( ターゲット・プラットフォーム(target platform) )の、各プラットフォームに関する情報を Docker では ARG 変数として定義済みです。ターゲット・プラットフォームは docker build--platform フラグで指定できます。

以下の ARG 変数は自動的に設定されます。

  • TARGETPLATFORM構築対象(build result) のプラットフォーム。例: linux/amd64linux/arm/v7windows/amd64
  • TARGETOS … TARGETPLATFORM の OS コンポーネント
  • TARGETARCH … TARGETPLATFORM のアーキテクチャ・コンポーネント
  • TARGETVARIANT … TARGETPLATFORM の派生コンポーネント
  • ``BUILDPLATFORM … 構築を処理するノードのプラットフォーム
  • BUILDOS … BUILDPLATFORM の OS コンポーネント
  • BUILDARCH … BUILDPLATFORM のアーキテクチャ・コンポーネント
  • BUILDVARIANT … BUILDPLATFORM の派生コンポーネント

これらの引数はグローバル スコープ(範囲)(scope) として定義されているため、構築ステージ内や、そのステージ内の RUN コマンドから自動的に利用できません。これらの引数を構築ステージの中で利用するには、値なしで定義します。

例:

FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"

構築キャッシュへの影響

ARG 変数は ENV 変数と異なり、構築イメージ内に保持されません。しかしながら、 ARG 変数も( ENV と)同じように構築キャッシュに影響を与えます。たとえば、 Dockerfile で以前に構築されたものと異なる ARG 変数が定義された場合は、定義がどうであろうと、真っ先に「 キャッシュ失敗(cache miss) 」が発生します。特に、全ての RUN 命令は ARG 命令で指定された ARG 変数を自動的に(環境変数として)扱います。つまり、これによって失敗が発生する可能性があります。全ての定義済み ARG 変数は、 Dockerfile 内で一致する ARG 命令がなければ、キャッシュから除外されます。

たとえば、これら2つの Dockerfile で考えましょう。

FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello

コマンドラインで --build-arg CONT_IMG_VER=<値> を指定すると、どちらの場合も2行目まではキャッシュ失敗は発生せず、3行目で失敗が発生します。 ARG CONT_IMG_VER によって、 RUN 行を CONT_IMG_VER=<value> echo hello として実行するように定義するのと同じです。つまり、 <値> が変わったため、キャッシュに失敗します。

同じコマンドラインで、別の例を考えます。

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=$CONT_IMG_VER
RUN echo $CONT_IMG_VER

この例では、3行目でキャッシュに失敗します。失敗するのは、 ENV にある変数の値が ARG 変数を参照し、かつ、その変数がコマンドラインを通して変更されるからです。この例では、 ENV 命令によってイメージの中に値が含まれます。

ENV 命令を、同じ名前の ARG で上書きする場合は、次のような Dockerifle になります。

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=hello
RUN echo $CONT_IMG_VER

3行目の CONT_IMG_VER 値は定数( hello )のため、キャッシュ失敗は発生しません。結果として、 RUN (4行目)で使われる環境変数と値は、構築の間は変わりません。

ONBUILD

ONBUILD [命令]

ONBUILD 命令は、後から他のイメージ構築の基礎(ベース)として使われる時に、「 トリガ(trigger) 」として実行する命令をイメージに追加します。トリガは後に続く(ダウンストリームの)コンテクストを構築時、ダウンストリームの Dockerfile にある FROM 命令の直後に、直ちに実行されます。

あらゆる構築命令をトリガとして登録できます。

他のイメージを構築する時に、その基礎となるイメージを構築するのに役立ちます。たとえば、アプリケーションの構築環境や、ユーザ固有の設定でカスタマイズ可能なデーモンです。

たとえば、イメージが再利用可能な Python アプリケーションを構築するもの(ビルダ)であれば、特定のディレクトリに対してアプリケーションのソースコードを追加する必要があり、さらに、後から構築用スクリプトを呼び出す必要がある場合もあるでしょう。この時点では ADDRUN 命令を呼び出せません。なぜなら、まだアプリケーションのソースコードにアクセスできず、さらに、アプリケーションごとにソースコードが異なるからです。これをシンプルに実現するには、アプリケーション開発者がテンプレートとなる Dockerfile をアプリケーションごとにコピー&ペーストする方法があります。しかしこれは、アプリケーション固有のコードが混在すると、効率が悪く、エラーも発生しがちであり、更新も困難です。

この解決策は ONBLUD 命令を使い、次の構築ステージ中に、後で実行する高度な命令を登録します。

これは、次のように動作します:

  1. ONBUILD 命令が発生すると、ビルダーは構築中のイメージ内に、トリガをメタデータとして追加します。現時点での構築では、この命令は何ら影響を与えません。
  2. 構築後、すべてのトリガ一覧は、イメージのマニフェスト OnBuild キー以下に保管されます。これらは docker inspect コマンドで調べられます。
  3. 後ほど、このイメージを新しい構築のベースとして用いるため、FROM 命令で呼び出されるでしょう。 FROM 命令の処理の一部として、ダウンストリームのビルダーは ONBUILD トリガを探します。そして、トリガが登録されたのと同じ順番で、トリガを実行します。もしもトリガの1つでも失敗すると、 FROM 命令は処理が中止となり、構築は失敗します。全てのトリガ処理が成功すると、 FROM 命令は完了し、以降は通常通り構築が進行します。
  4. 最後の処理がおわった後、最終イメージからトリガは削除されます。言い換えると、「 (grand-children) 」ビルドには継承されません。

たとえば、次のような追加となるでしょう。

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

警告

ONBUILD 命令をつなげ、 ONBUILD ONBUILD としては使えません。

警告

ONBLUID 命令は FROMMAINTAINER 命令をトリガにできません。

STOPSIGNAL

STOPSIGNAL 信号

STOPSIGNAL 命令は、 システムコール信号(system call signal) を設定し、コンテナを 終了(exit) するために送信されます。この信号(シグナル)は符号無し整数であり、カーネルの syscall 表の位置と一致します。たとえば、「9」であったり、 SIGKILL のような信号名の書式の 信号(signal) です。

HEALTHCHECK

HEALTHCHECK 命令は2つの形式があります。

  • HEALTHCHECK [オプション] CMD コマンド (コンテナ内部でコマンドを実行し、コンテナの正常性を確認)
  • HEALTHCHECK NONE (ベースイメージから、ヘルスチェック設定の継承を無効化)

HEALTHCHECK 命令は、Docker に対してコンテナのテスト方法を伝え、コンテナが動作し続けているかどうか確認します。これにより、ウェブサーバなどでサーバのプロセスが実行中にかかわらず、無限ループして詰まっていたり、新しい接続を処理できなかったりするような問題を検出します。

コンテナで ヘルスチェック(healthcheck) が指定されている場合は、通常のステータスに加え health status (ヘルスステータス)が追加されます。初期のステータスは starting (起動中)です。ヘルスチェックが正常であれば、(以前の状況にかかわらず)ステータスは healthy (正常)になります。何度か連続した 失敗(failure) があれば、ステータスは unhealthy (障害発生)になります。

CMD よりも前に書いて、オプションを指定できます。

... --interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s) --start-period=DURATION (default: 0s) --retries=N (default: 3)
  • --interval=期間 (デフォルト: 30s) ※ヘルスチェックの間隔
  • --timeout=期間 (デフォルト: 30s) ※タイムアウトの長さ
  • --start-period=期間 (デフォルト: 0s) ※ 開始時の間隔
  • --retries= (デフォルト: 3) ※リトライ回数

コンテナが起動し、(間隔で指定した) interval 秒後に、1回目のヘルスチェックを実行します。それから、再びヘルスチェックを前回のチェック完了後から、 interval 秒を経過するごとに行います。

信号を送信しても、 タイムアウト 秒まで処理に時間がかかっていれば、チェックに失敗したとみなされます。

コンテナに対するヘルスチェックの失敗が、 retries で指定したリトライ回数に達すると、コンテナは unhealthy とみなされます。

コンテナが起動に必要な時間を、初期化する時間として start period で指定します。この期間中に プローブ障害(probe failure) が発生したとしても、最大リトライ回数としてはカウントされません。ですが、 start period の期間中にヘルスチェックが成功するとコンテナは起動したとみなされ、以降に連続した失敗があれば、最大リトライ回数までカウントされます。

これらを指定できるのは、Dockerfile の HEALTHCHECK 命令のみです。複数の HEALTHCHECK 命令があれば、最後の1つのみ有効です。

CMD キーワードの後に書くコマンドは、シェルコマンド(例: HEALTHCHECK CMD /bin/check-running )か exec 配列(他の Dockerfile コマンドと同様です。詳細は ENTRYPOINT をご覧ください)のどちらか一方を使えます。

そのコマンドの終了ステータスが、対象となるコンテナのヘルスステータスになります。可能性のある値は、次の通りです。

  • 0: 成功(success) コンテナは正常
  • 1: ruby:障害 <unhealthy>
  • 2: ruby:予約済み <reserved> - この終了コードは使いません

たとえば、ウェブサーバがサイトのメインページを3秒以内に提供できるかどうかを、5分ごとにチェックするには、次の様にします。

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

ヘルスチェックの失敗時に調査(デバッグ)をしやすくするために、

コマンドが書き込んだ標準出力や標準エラー出力といった、あらゆる出力文字(UTF-8 エンコード方式)がヘルスステータスに保存され、これらは docker inspect で調べられます。この出力は短く保たれます(初めから 4096 バイトのみ保存します)。

コンテナのヘルスステータスが変われば、新しいステータスで health_status イベントが作成されます。

SHELL

SHELL ["実行ファイル", "パラメータ"]

SHELL 命令は、 シェル 形式で使われるデフォルトのコマンドを上書きできます。 Linux 上でのデフォルトのシェルは ["/bin/sh", "-c"] で、Windows は ["cmd", "/S", "/C"] です。Dockerfile では、 SHELL 命令を JSON 形式で書く必要があります。

Windows 上では特に SHELL 命令が役立ちます。これは、一般的に使われるシェルが cmdpowershell の2種類ありますし、 sh を含む他のシェルも利用できるからです。

SHELL 命令は何度も指定できます。それぞれの SHELL 命令は、以前すべての SHELL 命令を上書きし、以降の命令で(新しく指定したシェルが)有効になります。以下は例です。

FROM microsoft/windowsservercore

# 「cmd /S /C echo default」として実行
RUN echo default

# 「cmd /S /C powershell -command Write-Host default」として実行
RUN powershell -command Write-Host default

# 「powershell -command Write-Host hello」として実行
SHELL ["powershell", "-command"]
RUN Write-Host hello

# 「cmd /S /C echo hello」として実行
SHELL ["cmd", "/S", "/C"]
RUN echo hello

SHELL 命令によって効果があるのは、 Dockerfile でシェル形式として RUNCMDENTRYPOINT を使う時です。

以下の例は、Windows 上でよく見かける共通パターンであり、 SHELL 命令を使って効率化できます。

RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

Docker が呼び出すコマンドは、このようになります。

cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

これには、効率的ではない2つの理由があります。まず1つは、不要な cmd.exe コマンドのプロセッサ(シェル)が呼び出されるからです。2つめは、各 RUN 命令はシェル形式のため、コマンドの前に追加で powershell -command の実行が必要になるからです。

これを効率的にするには、2つの仕組みのどちらか1つを使います。1つは、次のように RUN 命令のコマンドを JSON 形式で書きます。

RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]

JSON 形式(で使うコマンドの指定)は明確であり、不要な cmd.exe を使いません。しかし、二重引用符(ダブルクォータ)やエスケープ処理が必要といった、冗長さがあります。もう1つの仕組みは、 SHELL 命令を使ってシェル形式にしますが、 escape パーサ・ディレクティブの指定があれば、 Windows ユーザにとって、より普通の書式で書けます(訳者捕捉: Dockerfile では、デフォルトのエスケープ文字は「」ですが、Windows の場合「」はパスの文字です。そのため、次の例のようにエスケープ文字を「`」などに変えると、Windows のパスがシェル形式でそのまま利用できるため、便利です)。

# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

結果は次のようになります。

PS E:\myproject> docker build -t shell .

Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
 ---> Running in 6fcdb6855ae2
 ---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in d0eef8386e97


    Directory: C:\


Mode         LastWriteTime              Length Name
----         -------------              ------ ----
d-----       10/28/2016  11:26 AM              Example


 ---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
 ---> Running in be6d8e63fe75
hello world
 ---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\myproject>

また、シェルの動作を変更するためにも SHELL 命令が使えます。たとえば、 Windows 上で SHELL cmd /S /C /V:ON|OFF を使えば、 遅延環境変数(delayed environment variable) の展開方法を切り替えられます。

さらに、SHELL 命令によって、Linux であれば zshcshtcsh といった、他のシェルに切り替えられます。

Dockerifle の例

Dockerfile の例は、以下をご覧ください。