読む時間の目安: 35 分
Dockerfile リファレンス
Docker は Dockerfile
から命令を読み込んで、自動的にイメージをビルドします。
Dockerfile
はテキストファイルであり、イメージを作り上げるために実行するコマンドラインコマンドを、すべてこのファイルに含めることができます。
docker build
を実行すると、順次コマンドライン命令を自動化した処理が行われて、ビルド結果となるイメージが得られます。
ここでは Dockerfile
において利用可能なコマンドを説明します。
このページを読み終えたら、さまざまなガイドとなる Dockerfile
ベストプラクティスを参照してください。
利用方法
docker buildコマンドは、Dockerfile
とコンテキスト(context)からイメージをビルドします。
ビルドにおけるコンテキストとは、指定されたPATH
またはURL
にある一連のファイルのことです。
PATH
はローカルファイルシステム内のディレクトリを表わします。
URL
は Git のリポジトリ URL のことです。
コンテキストは再帰的に処理されます。
つまり PATH
の場合はサブディレクトリがすべて含まれ、URL
の場合はリポジトリとそのサブモジュールが含まれます。
以下の例におけるビルドコマンドは、コンテキストとしてカレントディレクトリを用いるものです。
$ docker build .
Sending build context to Docker daemon 6.51 MB
...
ビルド処理は Docker デーモンが行うものであって CLI により行われるものではありません。 ビルド処理の開始時にまず行われるのは、コンテキスト全体を(再帰的に)デーモンに送信することです。 普通はコンテキストとして空のディレクトリを用意して、そこに Dockerfile を置きます。 そのディレクトリへは、Dockerfile の構築に必要となるファイルのみを置くようにします。
警告:
PATH
に対して root ディレクトリ/
を指定することはやめてください。 これを行うとビルド時に Docker デーモンに対して、ハードディスクの内容すべてを送り込むことになってしまいます。
ビルドコンテキスト内のファイルを利用する場合、Dockerfile
では命令を記述する際にファイル参照を指定します。
たとえば COPY
命令の対象として参照します。
ビルド時の処理性能を上げるために、コンテキストディレクトリ内に .dockerignore
ファイルを追加し、不要なファイルやディレクトリは除外するようにします。
詳しくはこのページ内の .dockerignore
ファイルの生成方法を参照してください。
慣例として Dockerfile
は Dockerfile
と命名されています。
またこのファイルはコンテキストディレクトリのトップに置かれます。
docker build
の -f
フラグを用いれば、Dockerfile がファイルシステム内のどこにあっても指定することができます。
$ docker build -f /path/to/a/Dockerfile .
イメージのビルドが成功した後の保存先として、リポジトリとタグを指定することができます。
$ docker build -t shykes/myapp .
ビルドの際に複数のリポジトリに対してイメージをタグづけするには、build
コマンドの実行時に -t
パラメーターを複数指定します。
$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
Dockerfile
内に記述されている命令を Docker デーモンが実行する際には、事前に Dockerfile
が検証され、文法の誤りがある場合にはエラーが返されます。
$ docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kB
Error response from daemon: Unknown instruction: RUNCMD
Docker デーモンは Dockerfile
内の命令を 1 つずつ実行し、必要な場合にはビルドイメージ内にその処理結果を確定します。
最後にビルドイメージの ID を出力します。
Docker デーモンは、送信されたコンテキスト内容を自動的にクリアします。
各命令は個別に実行されます。
それによって新たなイメージがビルドされます。
したがって、たとえば RUN cd /tmp
を実行したとしても、次の命令には何の効果も与えません。
Docker は可能な限り中間イメージ(キャッシュ)を再利用しようとします。
これは docker build
処理を速くするためです。
その場合は、端末画面に Using cache
というメッセージが出力されます。
(詳細については Dockerfile
ベストプラクティスガイドを参照してください。)
$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
---> 31f630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
---> Using cache
---> 2a1c91448f5f
Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/
---> Using cache
---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
---> Using cache
---> 7ea8aef582cc
Successfully built 7ea8aef582cc
ビルドキャッシュは、ローカルにて親イメージへのつながりを持ったイメージからのみ利用されます。
利用されるイメージとはつまり、前回のビルドによって生成されたイメージか、あるいは docker load
によってロードされたイメージのいずれかです。
ビルドキャッシュを特定のイメージから利用したい場合は --cache-from
オプションを指定します。
--cache-from
オプションが用いられた場合に、そのイメージは親イメージへのつながりを持っている必要はなく、他のレジストリから取得するイメージであっても構いません。
ビルドに関する操作を終えたら、次は リポジトリをレジストリへプッシュを読んでみてください。
BuildKit
Docker バージョン 18.09 から Docker ではビルド処理時のバックエンドを新たにサポートするようになりました。 これは moby/buildkit プロジェクトから提供されます。 BuildKit バックエンドは、それまでの実装に比べて数々の利点があります。 たとえば以下のようなものです。
- 未使用のビルドステージを検出して処理をスキップします。
- ビルドステージに関係なく並行処理を行います。
- ビルドを繰り返し行った場合には、ビルドコンテキスト内での変更ファイルのみを追加転送するようにします。
- ビルドコンテキスト内にて未使用のファイルを検出して転送をスキップします。
- 外部からの Dockerfile 実装を利用して多くの機能を実現します。
- これまでの API(中間イメージやコンテナー)に対する副作用を抑えます。
- ビルドキャッシュの自動的な削除(prune)に対して優先順位をつけます。
BuildKit バックエンドを利用するには、環境変数の設定として DOCKER_BUILDKIT=1
とすることが必要です。
これは CLI において docker build
を実行する前に行っておきます。
BuildKit ベースでビルドされたイメージにて利用可能な、実験的な Dockerfile 文法については、BuildKit リポジトリのドキュメントを参照してください。
記述書式
ここに Dockerfile
の記述書式を示します。
# Comment
INSTRUCTION arguments
命令(instruction)は大文字小文字を区別しません。 ただし慣習として大文字とします。 そうすることで引数(arguments)との区別をつけやすくします。
Docker は Dockerfile
内の命令を記述順に実行します。
Dockerfile
は必ずFROM
命令で始めなければなりません。
この命令より前に記述できるのは、パーサーディレクティブ、コメント、グローバル定義された ARG です。
FROM
命令は、ビルドするイメージに対しての 親イメージ を指定するものです。
FROM
よりも先に記述できる命令として ARG
があります。
これは FROM
において用いられる引数を宣言するものです。
行頭が #
で始まる行はコメントとして扱われます。
ただし例外としてパーサーディレクティブがあります。
行途中の #
は単なる引数として扱われます。
以下のような行記述が可能です。
# Comment
RUN echo 'we are running some # of cool things'
コメントにおいて行継続を指示する文字はサポートされていません。
パーサーディレクティブ
パーサーディレクティブ(parser directive)を利用することは任意です。
これは Dockerfile
内のその後に続く記述行を取り扱う方法を指示するものです。
パーサーディレクティブはビルドされるイメージにレイヤーを追加しません。
したがってビルドステップとして表示されることはありません。
パーサーディレクティブは、特別なコメントの記述方法をとるもので、# ディレクティブ=値
という書式です。
同一のディレクティブは一度しか記述できません。
コメント、空行、ビルド命令が一つでも読み込まれたら、それ以降 Docker はパーサーディレクティブの処理を行いません。
その場合、パーサーディレクティブの書式で記述されていても、それはコメントとして扱われます。
そしてパーサーディレクティブとして適切な書式であるかどうかも確認しません。
したがってパーサーディレクティブは Dockerfile
の冒頭に記述しなければなりません。
パーサーディレクティブは大文字小文字を区別しません。 ただし慣習として小文字とします。 同じく慣習として、パーサーディレクティブの次には空行を 1 行挿入します。 パーサーディレクティブにおいて、行継続を指示する文字はサポートされていません。
以上の規則により、下に示す例はすべて無効な記述となります。
行継続は無効です。
# direc \
tive=value
二度出現するため無効です。
# directive=value1
# directive=value2
FROM ImageName
ビルド命令の後に記述されたためコメントとして扱われます。
FROM ImageName
# directive=value
パーサーディレクティブではないコメントの後に記述されたためコメントして扱われます。
# About my dockerfile
# directive=value
FROM ImageName
不明なディレクティブは認識できないためコメントとして扱われます。 さらに正常なディレクティブであっても、その前にパーサーディレクティブではないコメントが記述された場合、コメントとして扱われます。
# unknowndirective=value
# knowndirective=value
改行ではないホワイトスペースは、パーサーディレクティブにおいて記述することができます。 そこで、以下の各行はすべて同一のものとして扱われます。
#directive=value
# directive =value
# directive= value
# directive = value
# dIrEcTiVe=value
パーサーディレクティブは以下のものがサポートされます。
syntax
escape
syntax
# syntax=[リモートイメージ参照]
たとえば以下のとおりです。
# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:1
# syntax=docker/dockerfile:1.0.0-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...
この機能は BuildKit バックエンドが用いられているときのみ利用可能です。
syntax ディレクティブは、現在の Dockerfile をビルドするために用いられる Dockerfile ビルダーのありかを定義するものです。 BuildKit バックエンドでは、外部実装されたビルダーをシームレスに利用できます。 このビルダーは Docker イメージとして提供されていて、サンドボックスコンテナー環境内で実行されます。
Dockerfile のカスタム実装では以下のことが可能となります。
- デーモンをアップデートしなくてもバグフィックスを自動的に行います。
- Dockerfile のビルドにあたって、全ユーザーが同一の実装を確実に利用するようにします。
- デーモンをアップデートしなくても最新機能を利用します。
- 新しい実験的な機能、あるいはサードパーティによる機能を試すことができます。
公式リリース
Docker では Docker Hub の docker/dockerfile
リポジトリを通じて、Dockerfile をビルドするために利用できる公式イメージを提供しています。
新たなイメージがリリースされるのは、安定版(stable)チャネルと実験版(experimental)チャネルです。
安定版(stable)チャネルではバージョン番号に意味をもたせています。 たとえば以下のとおりです。
- docker/dockerfile:1.0.0 - ただ 1 つの不変バージョン 1.0.0 を表わす
- docker/dockerfile:1.0 - バージョン 1.0.* を含む
- docker/dockerfile:1 - バージョン 1.. をすべて含む
- docker/dockerfile:latest - 安定版チャネルの最新リリース
実験版(experimental)チャネルでのバージョン番号は、リリースタイミングでの安定版チャネルのコンポーネントから、メジャー番号、マイナー番号を利用してナンバリングされています。
- docker/dockerfile:1.0.1-experimental - ただ 1 つの不変バージョン 1.0.1-experimental を表わす
- docker/dockerfile:1.0-experimental - バージョン 1.0 以降の最新実験版
- docker/dockerfile:experimental - 実験版チャネル上の最新版
作業の必要に応じて適切なチャネルを選んでください。
バグフィックスだけを望むのであれば docker/dockerfile:1.0
を利用します。
実験的な機能を利用したいなら、実験版チャネルを利用します。
実験版チャネルを利用している場合、リリースが進むほど互換性が失われるかもしれません。
したがって不変バージョンを利用することをお勧めします。
マスタービルドと最新機能リリースについては、ソースリポジトリの記述を参照してください。
escape
# escape=\ (バックスラッシュ)
または
# escape=` (バッククォート)
ディレクティブ escape
は、Dockerfile
内でエスケープ文字として用いる文字を設定します。
設定していない場合は、デフォルトとして \
が用いられます。
エスケープ文字は行途中での文字をエスケープするものと、行継続をエスケープするものがあります。
行継続のエスケープを使うと Dockerfile
内の命令を複数行に分けることができます。
Dockerfile
に escape
パーサーディレクティブを記述していたとしても、RUN
コマンドの途中でのエスケープは無効であり、行末の行継続エスケープのみ利用することができます。
Windows
においてはエスケープ文字を `
とします。
\
はディレクトリセパレーターとなっているためです。
`
は Windows PowerShell 上でも利用できます。
以下のような Windows
上の例を見てみます。
これはよく分からずに失敗してしまう例です。
2 行めの行末にある 2 つめの \
は、次の行への継続を表わすエスケープと解釈されます。
つまり 1 つめの \
をエスケープするものとはなりません。
同様に 3 行めの行末にある \
も、この行が正しく命令として解釈されるものであっても、行継続として扱われることになります。
結果としてこの Dockerfile の 2 行めと 3 行めは、一続きの記述行とみなされます。
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
この Dockerfile を用いると以下の結果になります。
PS C:\John> 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 C:\John>
上を解決するには COPY
命令と dir
の対象において /
を用います。
ただし Windows
上における普通のパス記述とは違う文法であるため混乱しやすく、さらに Windows
のあらゆるコマンドがパスセパレーターとして /
をサポートしているわけではないので、エラーになることもあります。
パーサーディレクティブ escape
を利用すれば、Windows
上のファイルパスの文法をそのままに、期待どおりに Dockerfile
が動作してくれます。
# escape=`
FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\
上を処理に用いると以下のようになります。
PS C:\John> 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 C:\John>
環境変数の置換
Dockerfile
のENV
構文により宣言される環境変数は、特定の命令において変数として解釈されます。
エスケープについても構文内にリテラルを含めることから、変数と同様の扱いと考えられます。
Dockerfile
における環境変数の記述書式は、$variable_name
あるいは ${variable_name}
のいずれかが可能です。
両者は同等のものですが、ブレースを用いた記述は ${foo}_bar
といった記述のように、変数名にホワイトスペースを含めないようにするために利用されます。
${variable_name}
という書式は、標準的な bash
の修飾書式をいくつかサポートしています。
たとえば以下のものです。
${variable:-word}
は、variable
が設定されているとき、この結果はその値となります。variable
が設定されていないとき、word
が結果となります。${variable:+word}
は、variable
が設定されているとき、この結果はword
となります。variable
が設定されていないとき、結果は空文字となります。
どの例においても、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
(上記のサポート対象の命令と組み合わせて用いる場合)
メモ: Docker バージョン 1.4 より以前では
ONBUILD
命令は環境変数をサポートしていません。 一覧にあげた命令との組み合わせで用いる場合も同様です。
環境変数の置換は、命令全体の中で個々の変数ごとに同一の値が用いられます。 これを説明するために以下の例を見ます。
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
この結果、def
は hello
になります。
bye
ではありません。
しかし ghi
は bye
になります。
ghi
を設定している行は、abc
に bye
を設定している命令と同一箇所ではないからです。
.dockerignore ファイル
Docker の CLI によってコンテキストが Docker デーモンに送信される前には、コンテキストのルートディレクトリの .dockerignore
というファイルが参照されます。
このファイルが存在したら、CLI はそこに記述されたパターンにマッチするようなファイルやディレクトリを除外した上で、コンテキストを扱います。
必要もないのに、巨大なファイルや取り扱い注意のファイルを不用意に送信してしまうことが避けられ、ADD
や COPY
を使ってイメージに間違って送信してしまうことを防ぐことができます。
CLI は .dockerignore
ファイルを各行ごとに区切られた設定一覧として捉えます。
ちょうど Unix シェルにおけるファイルグロブ(glob)と同様です。
マッチング処理の都合上、コンテキストのルートは、ワーキングディレクトリとルートディレクトリの双方であるものとしてみなされます。
たとえばパターンとして /foo/bar
と foo/bar
があったとすると、PATH
上であればサブディレクトリ foo
内、URL
であればその git レポジトリ内の、いずれも bar
というファイルまたはディレクトリを除外します。
その他のものについては除外対象としません。
.dockerignore
ファイルの各行頭の第 1 カラムめに #
があれば、その行はコメントとみなされて、CLI による解釈が行われず無視されます。
.dockerignore
ファイルの例を示します。
# comment
*/temp*
*/*/temp*
temp?
このファイルはビルドの際に以下にように動作します。
ルール | 処理結果 |
---|---|
# comment |
無視されます。 |
*/temp* |
ルートディレクトリの直下にあるサブディレクトリ内にて、temp で始まる名称のファイルまたはディレクトリすべてを除外します。たとえば通常のファイル /somedir/temporary.txt は除外されます。ディレクトリ /somedir/temp も同様です。 |
*/*/temp* |
ルートから 2 階層下までのサブディレクトリ内にて、temp で始まる名称のファイルまたはディレクトリすべてを除外します。たとえば /somedir/subdir/temporary.txt は除外されます。 |
temp? |
ルートディレクトリにあるファイルやディレクトリであって、temp にもう 1 文字ついた名前のものを除外します。たとえば /tempa や /tempb が除外されます。 |
パターンマッチングには Go 言語の filepath.Match ルールが用いられています。
マッチングの前処理として、文字列前後のホワイトスペースは取り除かれ、Go 言語の filepath.Clean によって .
と ..
が除外されます。
前処理を行った後の空行は無視されます。
Docker では Go 言語の filepath.Match ルールを拡張して、特別なワイルドカード文字列 **
をサポートしています。
これは複数のディレクトリ(ゼロ個を含む)にマッチします。
たとえば **/*.go
は、ファイル名が .go
で終わるものであって、どのサブディレクトリにあるものであってもマッチします。
ビルドコンテキストのルートも含まれます。
行頭を感嘆符 !
で書き始めると、それは除外に対しての例外を指定するものとなります。
以下の .dockerignore
の例はこれを用いるものです。
*.md
!README.md
マークダウンファイルがすべてコンテキストから除外されますが、README.md
だけは除外しません。
!
による例外ルールは、それを記述した位置によって処理に影響します。
特定のファイルが含まれるのか除外されるのかは、そのファイルがマッチする .dockerignore
内の最終の行によって決まります。
以下の例を考えてみます。
*.md
!README*.md
README-secret.md
コンテキストにあるマークダウンファイルはすべて除外されます。
例外として README ファイルは含まれることになりますが、ただし README-secret.md
は除外されます。
次の例も見てみます。
*.md
README-secret.md
!README*.md
README ファイルはすべて含まれます。
2 行めは意味をなしていません。
なぜなら !README*.md
には README-secret.md
がマッチすることになり、しかも !README*.md
が最後に記述されているからです。
.dockerignore
ファイルを使って Dockerfile
や .dockerignore
ファイルを除外することもできます。
除外したとしてもこの 2 つのファイルはデーモンに送信されます。
この 2 つのファイルはデーモンの処理に必要なものであるからです。
ただし ADD
命令や COPY
命令では、この 2 つのファイルはイメージにコピーされません。
除外したいファイルを指定するのではなく、含めたいファイルを指定したい場合があります。
これを実現するには、冒頭のマッチングパターンとして *
を指定します。
そしてこれに続けて、例外となるパターンを !
を使って指定します。
メモ: これまでの開発経緯によりパターン .
は無視されます。
FROM
FROM [--platform=<platform>] <image> [AS <name>]
または
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
または
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM
命令は、イメージビルドのための処理ステージを初期化し、ベースイメージを設定します。後続の命令がこれに続きます。
このため、正しい Dockerfile
は FROM
命令から始めなければなりません。
ベースイメージは正しいものであれば何でも構いません。
簡単に取り掛かりたいときは、公開リポジトリからイメージを取得します。
-
Dockerfile
内にてARG
は、FROM
よりも前に記述できる唯一の命令です。 ARG と FROM の関連についてを参照してください。 -
1 つの
Dockerfile
内にFROM
を複数記述することが可能です。 これは複数のイメージを生成するため、あるいは 1 つのビルドステージを使って依存イメージをビルドするために行います。 各FROM
命令までのコミットによって出力される最終のイメージ ID は書き留めておいてください。 個々のFROM
命令は、それ以前の命令により作り出された状態を何も変更しません。 -
オプションとして、新たなビルドステージに対しては名前をつけることができます。 これは
FROM
命令のAS name
により行います。 この名前は後続のFROM
やCOPY --from=<name|index>
命令において利用することができ、このビルドステージにおいてビルドされたイメージを参照します。 -
tag
とdigest
の設定はオプションです。 これを省略した場合、デフォルトであるlatest
タグが指定されたものとして扱われます。tag
の値に合致するものがなければ、エラーが返されます。
The optional --platform
flag can be used to specify the platform of the image
in case FROM
references a multi-platform image. For example, linux/amd64
,
linux/arm64
, or windows/amd64
. By default, the target platform of the build
request is used. Global build arguments can be used in the value of this flag,
for example automatic platform ARGs
allow you to force a stage to native build platform (--platform=$BUILDPLATFORM
),
and use it to cross-compile to the target platform inside the stage.
ARG と FROM の関連について
FROM
命令では、ARG
命令によって宣言された変数すべてを参照できます。
この 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
以降の命令において利用することはできません。
初出の FROM
よりも前に宣言された ARG
の値を利用するには、ビルドステージ内において ARG
命令を、値を設定することなく利用します。
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
RUN
RUN には 2 つの書式があります。
RUN <command>
(シェル形式、コマンドはシェル内で実行される、シェルとはデフォルトで Linux なら/bin/sh -c
、Windows ならcmd /S /C
)RUN ["executable", "param1", "param2"]
(exec 形式)
RUN
命令は、現在のイメージの最上位の最新レイヤーにおいて、あらゆるコマンドを実行します。
そして処理結果を確定します。
結果が確定したイメージは、Dockerfile
の次のステップにおいて利用されていきます。
RUN
命令をレイヤー上にて扱い、処理確定を行うこの方法は、Docker の根本的な考え方に基づいています。
この際の処理確定は容易なものであって、イメージの処理履歴上のどの時点からでもコンテナーを復元できます。
この様子はソース管理システムに似ています。
exec 形式は、シェル文字列が置換されないようにします。
そして RUN
の実行にあたっては、特定のシェル変数を含まないベースイメージを用います。
シェル形式にて用いるデフォルトのシェルを変更するには SHELL
コマンドを使います。
シェル形式においては \
(バックスラッシュ)を用いて、1 つの RUN 命令を次行にわたって記述することができます。
たとえば以下のような 2 行があるとします。
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
上は 2 行を合わせて、以下の 1 行としたものと同じです。
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
メモ: ‘/bin/sh’ 以外の別のシェルを利用する場合は、exec 形式を用いて、目的とするシェルを引数に与えます。 たとえば
RUN ["/bin/bash", "-c", "echo hello"]
とします。
メモ: exec 形式は JSON 配列として解釈されます。 したがって文字列をくくるのはダブルクォート(”)であり、シングルクォート(’)は用いてはなりません。
メモ: シェル形式とは違って exec 形式はコマンドシェルを起動しません。 これはつまり、ごく普通のシェル処理とはならないということです。 たとえば
RUN [ "echo", "$HOME" ]
を実行したとすると、$HOME
の変数置換は行われません。 シェル処理が行われるようにしたければ、シェル形式を利用するか、あるいはシェルを直接実行するようにします。 たとえばRUN [ "sh", "-c", "echo $HOME" ]
とします。 exec 形式によってシェルを直接起動した場合、シェル形式の場合でも同じですが、変数置換を行うのはシェルであって、docker ではありません。
メモ: JSON 記述において、バックスラッシュはエスケープする必要があります。 特に関係してくるのは Windows であり、Windows ではパスセパレーターにバックスラッシュを用います。
RUN ["c:\windows\system32\tasklist.exe"]
という記述例は、適正な JSON 記述ではないことになるため、シェル形式として扱われ、思いどおりの動作はせずエラーとなります。 正しくはRUN ["c:\\windows\\system32\\tasklist.exe"]
と記述します。
RUN
命令に対するキャッシュは、次のビルドの際、その無効化は自動的に行われません。
RUN apt-get dist-upgrade -y
のような命令に対するキャッシュは、次のビルドの際にも再利用されます。
RUN
命令に対するキャッシュを無効にするためには --no-cache
フラグを利用します。
たとえば docker build --no-cache
とします。
詳しくは Dockerfile
ベストプラクティスガイドを参照してください。
RUN
命令に対するキャッシュは ADD
命令を使うと無効になります。
詳しくは以下を参照してください。
(RUN に関する)既知の問題
-
Issue 783 はファイルパーミッションに関する問題を取り上げていて、ファイルシステムに AUFS を用いている場合に発生します。 たとえば
rm
によってファイルを削除しようとしたときに、これが発生する場合があります。aufs の最新バージョンを利用するシステム(つまりマウントオプション
dirperm1
を設定可能なシステム)の場合、docker はレイヤーに対してdirperm1
オプションをつけてマウントすることで、この問題を自動的に解消するように試みます。dirperm1
オプションに関する詳細はaufs
の man ページ を参照してください。dirperm1
をサポートしていないシステムの場合は、issue に示される回避方法を参照してください。
CMD
CMD
には 3 つの書式があります。
CMD ["executable","param1","param2"]
(exec 形式、この形式が推奨される)CMD ["param1","param2"]
(ENTRYPOINT
のデフォルトパラメーターとして)CMD command param1 param2
(シェル形式)
Dockerfile
では CMD
命令を 1 つしか記述できません。
仮に複数の CMD
を記述しても、最後の CMD
命令しか処理されません。
CMD
命令の主目的は、コンテナーの実行時のデフォルト処理を設定することです。
この処理設定においては、実行モジュールを含める場合と、実行モジュールを省略する場合があります。
省略する場合は ENTRYPOINT
命令を合わせて指定する必要があります。
メモ:
ENTRYPOINT
命令に対するデフォルト引数を設定する目的でCMD
命令を用いる場合、CMD
とENTRYPOINT
の両命令とも、JSON 配列形式で指定しなければなりません。
メモ: exec 形式は JSON 配列として解釈されます。 したがって文字列をくくるのはダブルクォート(”)であり、シングルクォート(’)は用いてはなりません。
メモ: シェル形式とは違って exec 形式はコマンドシェルを起動しません。 これはつまり、ごく普通のシェル処理とはならないということです。 たとえば
RUN [ "echo", "$HOME" ]
を実行したとすると、$HOME
の変数置換は行われません。 シェル処理が行われるようにしたければ、シェル形式を利用するか、あるいはシェルを直接実行するようにします。 たとえばRUN [ "sh", "-c", "echo $HOME" ]
とします。 exec 形式によってシェルを直接起動した場合、シェル形式の場合でも同じですが、変数置換を行うのはシェルであって、docker ではありません。
シェル形式または exec 形式を用いる場合、CMD
命令は、イメージが起動されたときに実行するコマンドを指定します。
シェル形式を用いる場合、<command>
は /bin/sh -c
の中で実行されます。
FROM ubuntu
CMD echo "This is a test." | wc -
<command>
をシェル実行することなく実行したい場合は、そのコマンドを JSON 配列として表現し、またそのコマンドの実行モジュールへのフルパスを指定しなければなりません。
この配列書式は CMD
において推奨される記述です。
パラメーターを追加する必要がある場合は、配列内にて文字列として記述します。
FROM ubuntu
CMD ["/usr/bin/wc","--help"]
コンテナーにおいて毎回同じ実行モジュールを起動させたい場合は、CMD
命令と ENTRYPOINT
命令を合わせて利用することを考えてみてください。
ENTRYPOINT を参照のこと。
docker run
において引数を指定することで、CMD
命令に指定されたデフォルトを上書きすることができます。
メモ:
RUN
とCMD
を混同しないようにしてください。RUN
は実際にコマンドが実行されて、結果を確定させます。 一方CMD
はイメージビルド時には何も実行しません。 イメージに対して実行する予定のコマンドを指示するものです。
LABEL
LABEL <key>=<value> <key>=<value> <key>=<value> ...
LABEL
命令はイメージに対してメタデータを追加します。
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 行で記述することにより、ビルドされるイメージのサイズが軽減されていましたが、今はそのようなことはありません。 それでも、複数ラベルを 1 つの命令として記述しても構いません。 つまり以下のように 2 つの方法をとることができます。
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
ベースイメージまたは親イメージ(FROM
命令で指定されたイメージ)に含まれるラベルは、ビルドしようとしているイメージに継承されます。
同一のラベル設定が存在していて、異なる値が設定されていた場合は、最終的に設定される値によって古い値は上書きされます。
イメージのラベルを参照するには docker inspect
コマンドを用います。
"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 <name>
MAINTAINER
命令は、ビルドされるイメージの Author フィールドを設定します。
LABEL
命令を使った方がこれよりも柔軟に対応できるため、LABEL
を使うようにします。
そうすれば必要なメタデータとしてどのようにでも設定ができて、docker inspect
を用いて簡単に参照することができます。
MAINTAINER
フィールドに相当するラベルを作るには、以下のようにします。
LABEL maintainer="SvenDowideit@home.org.au"
こうすれば docker inspect
によってラベルをすべて確認することができます。
EXPOSE
EXPOSE <port> [<port>/<protocol>...]
EXPOSE
命令はコンテナーの実行時に、所定ネットワーク上のどのポートをリッスンするかを指定します。
リッスンするポートは TCP、UDP のいずれでも指定可能です。
プロトコルが指定されていない場合は、デフォルトとして TCP が採用されます。
EXPOSE
命令は実際にはポートを公開するものではありません。
これはイメージの作成者とコンテナーの実行者との間で取り交わす、言ってみればメモです。
どのポートを公開するつもりでいるのかを表わしています。
コンテナーが実行されるときに実際にポートを公開するには、docker run
の -p
フラグを利用します。
これによって複数のポートへの割り当てが行われます。
あるいは -P
フラグを用いると、すべてのポートを公開し、ホストの高位のポートに割り当てます。
デフォルトで EXPOSE
は TCP を想定していますが、UDP を指定することもできます。
EXPOSE 80/udp
TCP と UDP の両方を公開する場合は、以下のような 2 行を記述します。
EXPOSE 80/tcp
EXPOSE 80/udp
これに対して docker run
に -P
をつけて実行すると、一度は TCP のポートが開き、また別のときに UDP のポートが開きます。
-P
を用いると、ホスト上の高位のポートが順次(ephemeralに)割り当てられます。
したがって TCP と UDP のポートは同一とはなりません。
EXPOSE
による設定を行っていても、実行時に -p
フラグを使って上書き指定することができます。
たとえば以下のとおりです。
docker run -p 80:80/tcp -p 80:80/udp ...
ホストシステム上においてポート転送を行う場合は、-P フラグの利用を参照してください。
docker network
コマンドでは、コンテナー間の通信を行うネットワークの生成をサポートします。
このネットワークでは公開するポートの指定を必要としません。
なぜならネットワークに接続されるコンテナーは、ポートとは関係なく互いに通信ができるようになっています。
詳しくはネットワーク機能の概要を参照してください。
ENV
ENV <key> <value>
ENV <key>=<value> ...
ENV
命令は、環境変数 <key>
に <value>
という値を設定します。
ビルドステージ内の後続命令の環境において、環境変数の値は維持されます。
また、いろいろとインラインにて変更することもできます。
ENV
命令には 2 つの書式があります。
1 つめの書式は ENV <key> <value>
です。
1 つの変数に対して 1 つの値を設定します。
全体の文字列のうち、最初の空白文字以降がすべて <value>
として扱われます。
そこにはホワイトスペース文字を含んでいて構いません。
この値は他の環境変数において用いられることも考えられます。
したがってクォート文字は、エスケープされていなければ無視されます。
2 つめの書式は ENV <key>=<value> ...
です。
これは一度に複数の値を設定できる形です。
この書式では等号(=)を用いており、1 つめの書式とは異なります。
コマンドライン上の解析で行われることと同じように、クォートやバックスラッシュを使えば、値の中に空白などを含めることができます。
たとえば以下のとおりです。
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
そして以下です。
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
上の 2 つは最終的に同じ結果をイメージに書き入れます。
ENV
を用いて設定された環境変数は、そのイメージから実行されたコンテナーであれば維持されます。
環境変数の参照は docker inspect
を用い、値の変更は docker run --env <key>=<value>
により行うことができます。
メモ: 環境変数が維持されると、思わぬ副作用を引き起こすことがあります。 たとえば
ENV DEBIAN_FRONTEND noninteractive
という設定を行なっていると、Debian ベースのイメージにおいて apt-get を使う際には混乱を起こすかもしれません。 1 つのコマンドには 1 つの値のみを設定するにはRUN <key>=<value> <command>
を実行します。
ADD
ADD には 2 つの書式があります。
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
(この書式はホワイトスペースを含むパスを用いる場合に必要)
メモ:
--chown
による機能は Linux コンテナーをビルドする Dockerfile においてのみサポートされるものであり、Windows コンテナーに対しては動作しません。 Linux と Windows の間で、ユーザーやグループの所有権という考え方が共有されないためです。 この機能では/etc/passwd
や/etc/group
を使って、ユーザーやグループの名前を ID に変換するため、この機能が実現できるのは Linux OS をベースとするコンテナーに限定されます。
ADD
命令は <src>
に示されるファイル、ディレクトリ、リモートファイル URL をコピーして、イメージ内のファイルシステム上のパス <dest>
にこれらを加えます。
<src>
には複数のソースを指定することが可能です。
ソースとしてファイルあるいはディレクトリが指定されている場合、そのパスはビルドコンテキストからの相対パスとして解釈されます。
<src>
にはワイルドカードを含めることができます。
その場合、マッチング処理は Go 言語の filepath.Match ルールに従って行われます。
記述例は以下のとおりです。
ADD hom* /mydir/ # "hom" で始まるファイルすべてを追加します。
ADD hom?.txt /mydir/ # ? は 1 文字にマッチします。たとえば "home.txt" にマッチします。
<dest>
は絶対パスか、あるいは WORKDIR
からの相対パスにより指定します。
対象としているコンテナー内において、そのパスに対してソースがコピーされます。
ADD test relativeDir/ # "test" を `WORKDIR`/relativeDir/ へ追加します。
ADD test /absoluteDir/ # "test" を /absoluteDir/ へ追加します。
ファイルやディレクトリを追加する際に、その名前の中に([
や ]
のような)特殊な文字が含まれている場合は、Go 言語のルールに従ってパス名をエスケープする必要があります。
これはパターンマッチングとして扱われないようにするものです。
たとえば arr[0].txt
というファイルを追加する場合は、以下のようにします。
ADD arr[[]0].txt /mydir/ # "arr[0].txt" というファイルを /mydir/ へコピーします。
ADD されるファイルやディレクトリの UID と GID は、すべて 0 として生成されます。
ただしオプションとして --chown
フラグを用いると、引数に与えたユーザー空間、グループ名、あるいは UID と GID の組み合わせによる指定が可能になり、特定の所有権を満たした ADD を行うことができます。
--chown
フラグの書式には、ユーザー名とグループ名を文字列で指定するか、あるいは直接 UID、GID の数値をそれぞれに指定することができます。
グループ名を指定せずにユーザー名を指定した場合、あるいは GID を指定せずに UID を指定した場合は、GID と同じ値が UID に対して用いられます。
ユーザー名やグループ名が指定された場合、コンテナー内の root ファイルシステム配下にある /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/
コンテナー内の root ファイルシステムに /etc/passwd
や /etc/group
が存在せず、その状態で --chown
フラグが用いられた場合、ADD
命令の処理のところでビルドが失敗します。
数値により ID を指定した場合は、上のファイルを探すことが不要となるため、コンテナーの root ファイルシステム内に上のファイルがあってもなくても、関係がなくなります。
<src>
にリモートファイル URL が指定された場合、コピー先のパーミッションは 600 となります。
リモートファイルの取得時に HTTP の Last-Modified
ヘッダーが含まれている場合は、ヘッダーに書かれたタイムスタンプを利用して、コピー先ファイルの mtime
を設定します。
ただし ADD
によって処理されるファイルが何であっても、ファイルが変更されたかどうか、そしてキャッシュを更新するべきかどうかは mtime
によって判断されるわけではありません。
メモ:
Dockerfile
を標準入力から生成する場合(docker build - < somefile
)は、ビルドコンテキストが存在していないことになるので、ADD
命令には URL の指定しか利用できません。 また標準入力から圧縮アーカイブを入力する場合(docker build - < archive.tar.gz
)は、そのアーカイブのルートとその配下のファイルが、ビルド時のコンテキストとなります。
メモ: URL ファイルが認証によって保護されている場合は、
RUN wget
やRUN curl
あるいは同様のツールをコンテナー内から利用する必要があります。ADD
命令は認証処理をサポートしていません。
メモ:
ADD
命令の<src>
の内容が変更されていた場合、そのADD
命令以降に続く命令のキャッシュはすべて無効化されます。 そこにはRUN
命令に対するキャッシュの無効化も含まれます。 詳しくはDockerfile
ベストプラクティスガイドを参照してください。
ADD
命令は以下のルールに従います。
-
<src>
のパス指定は、ビルドコンテキスト内でなければならないため、たとえばADD ../something /something
といったことはできません。docker build
の最初の処理ステップでは、コンテキストディレクトリ(およびそのサブディレクトリ)を Docker デーモンに送信するところから始まるためです。 -
<src>
が URL 指定であって<dest>
の最後にスラッシュが指定されていない場合、そのファイルを URL よりダウンロードして<dest>
にコピーします。 -
<src>
が URL 指定であって<dest>
の最後にスラッシュが指定された場合、ファイルが指定されたものとして扱われ、URL からダウンロードして<dest>/<filename>
にコピーします。 たとえばADD http://example.com/foobar /
という記述は/foobar
というファイルを作ることになります。 URL には正確なパス指定が必要です。 上の記述であれば、適切なファイルが見つけ出されます。 (http://example.com
では正しく動作しません。) -
<src>
がディレクトリである場合、そのディレクトリの内容がすべてコピーされます。 ファイルシステムのメタデータも含まれます。
メモ: ディレクトリそのものはコピーされません。 コピーされるのはその中身です。
-
<src>
がローカルにある tar アーカイブであって、認識できるフォーマット(gzip、bzip2、xz)である場合、1 つのディレクトリ配下に展開されます。 リモートURL の場合は展開されません。 ディレクトリのコピーあるいは展開の仕方は、tar -x
と同等です。 つまりその結果は以下の 2 つのいずれかに従います。- コピー先に指定されていれば、それが存在しているかどうかに関わらず。あるいは、
- ソースツリーの内容に従って各ファイルごとに行う。衝突が発生した場合は 2. を優先する。
メモ: 圧縮されたファイルが認識可能なフォーマットであるかどうかは、そのファイル内容に基づいて確認されます。 名前によって判断されるわけではありません。 たとえば、空のファイルの名前の末尾がたまたま
.tar.gz
となっていた場合、圧縮ファイルとして認識されないため、解凍に失敗したといったエラーメッセージは一切出ることはなく、このファイルはコピー先に向けて単純にコピーされるだけです。 -
<src>
が上に示す以外のファイルであった場合、メタデータも含めて個々にコピーされます。 このとき<dest>
が/
で終わっていたらディレクトリとみなされるので、<src>
の内容は<dest>/base(<src>)
に書き込まれることになります。 -
複数の
<src>
が直接指定された場合、あるいはワイルドカードを用いて指定された場合、<dest>
はディレクトリとする必要があり、末尾には/
をつけなければなりません。 -
<dest>
の末尾にスラッシュがなかった場合、通常のファイルとみなされるため、<src>
の内容が<dest>
に書き込まれます。 -
<dest>
のパス内のディレクトリが存在しなかった場合、すべて生成されます。
COPY
COPY には 2 つの書式があります。
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
(パスにホワイトスペースを含む場合にこの書式が必要)
メモ:
--chown
による機能は Linux コンテナーをビルドする Dockerfile においてのみサポートされるものであり、Windows コンテナーに対しては動作しません。 Linux と Windows の間で、ユーザーやグループの所有権という考え方が共有されないためです。 この機能では/etc/passwd
や/etc/group
を使って、ユーザーやグループの名前を ID に変換するため、この機能が実現できるのは Linux OS をベースとするコンテナーに限定されます。
COPY
命令は <src>
からファイルやディレクトリを新たにコピーして、コンテナー内のファイルシステムのパス <dest>
に追加します。
<src>
には複数のソースを指定することが可能です。
ソースとしてファイルあるいはディレクトリが指定されている場合、そのパスはビルドコンテキストからの相対パスとして解釈されます。
<src>
にはワイルドカードを含めることができます。
その場合、マッチング処理は Go 言語の filepath.Match ルールに従って行われます。
記述例は以下のとおりです。
COPY hom* /mydir/ # "hom" で始まるファイルすべてを追加します。
COPY hom?.txt /mydir/ # ? は 1 文字にマッチします。たとえば "home.txt" にマッチします。
<dest>
は絶対パスか、あるいは WORKDIR
からの相対パスにより指定します。
対象としているコンテナー内において、そのパスに対してソースがコピーされます。
COPY test relativeDir/ # "test" を `WORKDIR`/relativeDir/ へ追加します。
COPY test /absoluteDir/ # "test" を /absoluteDir/ へ追加します。
ファイルやディレクトリをコピーする際に、その名前の中に([
や ]
のような)特殊な文字が含まれている場合は、Go 言語のルールに従ってパス名をエスケープする必要があります。
これはパターンマッチングとして扱われないようにするものです。
たとえば arr[0].txt
というファイルをコピーする場合は、以下のようにします。
COPY arr[[]0].txt /mydir/ # "arr[0].txt" というファイルを /mydir/ へコピーします。
コピーされるファイルやディレクトリの UID と GID は、すべて 0 として生成されます。
ただしオプションとして --chown
フラグを用いると、引数に与えたユーザー空間、グループ名、あるいは UID と GID の組み合わせによる指定が可能になり、特定の所有権を満たしたコピーを行うことができます。
--chown
フラグの書式には、ユーザー名とグループ名を文字列で指定するか、あるいは直接 UID、GID の数値をそれぞれに指定することができます。
グループ名を指定せずにユーザー名を指定した場合、あるいは GID を指定せずに UID を指定した場合は、GID と同じ値が UID に対して用いられます。
ユーザー名やグループ名が指定された場合、コンテナー内の root ファイルシステム配下にある /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/
コンテナー内の root ファイルシステムに /etc/passwd
や /etc/group
が存在せず、その状態で --chown
フラグが用いられた場合、COPY
命令の処理のところでビルドが失敗します。
数値により ID を指定した場合は、上のファイルを探すことが不要となるため、コンテナーの root ファイルシステム内に上のファイルがあってもなくても、関係がなくなります。
メモ:
Dockerfile
を標準入力から生成する場合(docker build - < somefile
)は、ビルドコンテキストが存在していないことになるので、COPY
命令は利用することができません。
オプションとして COPY
にはフラグ --from=<name|index>
があります。
これは実行済のビルドステージ(FROM .. AS <name>
により生成)におけるソースディレクトリを設定するものです。
これがあると、ユーザーが指定したビルドコンテキストのかわりに、設定されたディレクトリが用いられます。
このフラグは数値インデックスを指定することも可能です。
この数値インデックスは、FROM
命令から始まる実行済のビルドステージすべてに割り当てられている値です。
指定されたビルドステージがその名前では見つけられなかった場合、指定された数値によって見つけ出します。
COPY
命令は以下のルールに従います。
-
<src>
のパス指定は、ビルドコンテキスト内でなければならないため、たとえばCOPY ../something /something
といったことはできません。docker build
の最初の処理ステップでは、コンテキストディレクトリ(およびそのサブディレクトリ)を Docker デーモンに送信するところから始まるためです。 -
<src>
がディレクトリである場合、そのディレクトリ内の内容がすべてコピーされます。 ファイルシステムのメタデータも含まれます。
メモ: ディレクトリそのものはコピーされません。 コピーされるのはその中身です。
-
<src>
が上に示す以外のファイルであった場合、メタデータも含めて個々にコピーされます。 このとき<dest>
が/
で終わっていたらディレクトリとみなされるので、<src>
の内容は<dest>/base(<src>)
に書き込まれることになります。 -
複数の
<src>
が直接指定された場合、あるいはワイルドカードを用いて指定された場合、<dest>
はディレクトリとする必要があり、末尾には/
をつけなければなりません。 -
<dest>
の末尾にスラッシュがなかった場合、通常のファイルとみなされるため、<src>
の内容が<dest>
に書き込まれます。 -
<dest>
のパス内のディレクトリが存在しなかった場合、すべて生成されます。
ENTRYPOINT
ENTRYPOINT には 2 つの書式があります。
ENTRYPOINT ["executable", "param1", "param2"]
(exec 形式、この形式を推奨)ENTRYPOINT command param1 param2
(シェル形式)
ENTRYPOINT
は、コンテナーを実行モジュールのようにして実行する設定を行うものです。
たとえば以下の例では、nginx をデフォルト設定で起動します。 ポートは 80 番を利用します。
docker run -i -t --rm -p 80:80 nginx
docker run <image>
に対するコマンドライン引数は、exec 形式の ENTRYPOINT
の指定要素の後に付け加えられます。
そして CMD
において指定された引数は上書きされます。
これはつまり、引数をエントリーポイントに受け渡すことができるということです。
たとえば docker run <image> -d
としたときの -d
は、引数としてエントリーポイントに渡されます。
docker run --entrypoint
を利用すれば ENTRYPOINT
の内容を上書きすることができます。
シェル形式では CMD
や run
によるコマンドライン引数は受け付けずに処理を行います。
ただし ENTRYPOINT
が /bin/sh -c
のサブコマンドとして起動されるので、シグナルを送信しません。
これはつまり、実行モジュールがコンテナーの PID 1
にはならず、Unix のシグナルを受信しないということです。
したがって docker stop <container>
が実行されても、その実行モジュールは SIGTERM
を受信しないことになります。
ENTRYPOINT
命令は複数記述されていても、最後の命令しか処理されません。
exec 形式の ENTRYPOINT の例
ENTRYPOINT
の exec 形式は、デフォルト実行するコマンドと引数として、ほぼ変わることがないものを設定します。
そして CMD
命令の 2 つある書式のいずれでもよいので、変更が必要になりそうな内容を追加で設定します。
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
コンテナーを実行すると、ただ 1 つのプロセスとして 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"]
1 つの実行モジュールを起動するスクリプトを書く場合、最終実行される実行モジュールが Unix シグナルを受信できるようにするには exec
あるいは gosu
を用います。
#!/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 "$@"
シャットダウンの際に追加でクリーンアップするようなコマンドを実行したい(他のコンテナーとの通信を行ないたい)場合、あるいは複数の実行モジュールを連動して動かしている場合は、ENTRYPOINT
のスクリプトが確実に Unix シグナルを受信し、これを受けて動作するようにすることが必要になるかもしれません。
#!/bin/sh
# メモ: ここで sh を用いました。したがって busybox コンテナーでも動作します。
# ここで trap を用います。サービスが停止した後に手動でクリーンアップする
# コマンドを実行するにはこれも必要となります。
# こうしておかないと、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 exec
や docker top
を使って確認することができます。
そしてこのスクリプトから Apache を停止させます。
$ 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 形式はコマンドシェルを起動しません。 これはつまり、ごく普通のシェル処理とはならないということです。 たとえば
RUN [ "echo", "$HOME" ]
を実行したとすると、$HOME
の変数置換は行われません。 シェル処理が行われるようにしたければ、シェル形式を利用するか、あるいはシェルを直接実行するようにします。 たとえばRUN [ "sh", "-c", "echo $HOME" ]
とします。 exec 形式によってシェルを直接起動した場合、シェル形式の場合でも同じですが、変数置換を行うのはシェルであって、docker ではありません。
シェル形式の ENTRYPOINT の例
ENTRYPOINT
に指定した文字列は、そのまま /bin/sh -c
の中で実行されます。
この形式は、シェル環境変数を置換しながらシェル処理を実行します。
そして CMD
や docker 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
ENTRYPOINT
によって指定された top
の出力は 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 の関連について
CMD
命令も ENTRYPOINT
命令も、ともにコンテナー起動時に実行するコマンドを定義するものです。
両方が動作する際に必要となるルールがいくらかあります。
-
Dockerfile には、
CMD
またはENTRYPOINT
のいずれかが、少なくとも 1 つ必要です。 -
ENTRYPOINT
は、コンテナーを実行モジュールとして実行する際に利用します。 -
CMD
は、ENTRYPOINT
のデフォルト引数を定義するため、あるいはその時点でのみコマンド実行を行うために利用します。 -
CMD
はコンテナー実行時に、別の引数によって上書きされることがあります。
以下の表は、ENTRYPOINT
と CMD
の組み合わせに従って実行されるコマンドを示しています。
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_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | 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 | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
メモ:
CMD
がベースイメージにて定義されていた場合、ENTRYPOINT
を設定するとCMD
を空の値にリセットします。 そのような場合、CMD
へは現イメージにおいて定義を行い、値を持たせておくことが必要です。
VOLUME
VOLUME ["/data"]
VOLUME
命令は指定された名前を使ってマウントポイントを生成します。
そして自ホストまたは他のコンテナーからマウントされたボリュームとして、そのマウントポイントを扱います。
指定する値は JSON 配列として VOLUME ["/var/log/"]
のようにするか、あるいは単純な文字列を複数与えます。
たとえば VOLUME /var/log
や VOLUME /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 配列として扱われます。 したがって文字列をくくるのはダブルクォート(
"
)であり、シングルクォート('
)は用いてはなりません。 -
コンテナー実行時に宣言されるホストディレクトリ: ホストディレクトリ(マウントポイント)は、その性質からして、ホストに依存するものです。 これはイメージの可搬性を確保するためなので、設定されたホストディレクトリが、あらゆるホスト上にて利用可能になるかどうかの保証はありません。 このため、Dockerfile の内部からホストディレクトリをマウントすることはできません。 つまり
VOLUME
命令はhost-dir
(ホストのディレクトリを指定する)パラメーターをサポートしていません。 マウントポイントの指定は、コンテナーを生成、実行するときに行う必要があります。
USER
USER <user>[:<group>] または
USER <UID>[:<GID>]
USER
命令は、ユーザー名(または UID)と、オプションとしてユーザーグループ(または GID)を指定します。
そしてイメージが実行されるとき、Dockerfile
内の後続の RUN
、CMD
、ENTRYPOINT
の各命令においてこの情報を利用します。
警告: ユーザーにプライマリーグループがない場合、イメージ(あるいは次の命令)は
root
グループとして実行されます。
Windows において、ビルトインアカウント以外のユーザーを利用する場合は、あらかじめユーザーを生成しておく必要があります。 Dockerfile 内において
net user
コマンドを用いれば、これを行うことができます。
FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick
WORKDIR
WORKDIR /path/to/workdir
WORKDIR
命令はワークディレクトリを設定します。
Dockerfile
内にてその後に続く RUN
、CMD
、ENTRYPOINT
、COPY
、ADD
の各命令において利用することができます。
WORKDIR
が存在しないときは生成されます。
これはたとえ、この後にワークディレクトリが利用されていなくても生成されます。
WORKDIR
命令は Dockerfile
内にて複数利用することができます。
ディレクトリ指定に相対パスが用いられた場合、そのパスは、直前の 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 <name>[=<default value>]
ARG
命令は変数を定義して、ビルド時にその値を受け渡します。
これは docker build
コマンドにおいて --build-arg <varname>=<value>
フラグを利用して行います。
指定したビルド引数(build argument)が Dockerfile 内において定義されていない場合は、ビルド処理時に警告メッセージが出力されます。
[Warning] One or more build-args [foo] were not consumed.
Dockerfile には複数の ARG
命令を含めることもできます。
たとえば以下の Dockerfile は有効な例です。
FROM busybox
ARG user1
ARG buildno
...
警告: ビルド時の変数として、github キーや認証情報などの秘密の情報を設定することは、お勧めできません。 ビルド変数の値は、イメージを利用する他人が
docker history
コマンドを実行すれば容易に見ることができてしまうからです。
デフォルト値
ARG
命令にはオプションとしてデフォルト値を設定することができます。
FROM busybox
ARG user1=someuser
ARG buildno=1
...
ARG
命令にデフォルト値が設定されていて、ビルド時に値設定が行われなければ、デフォルト値が用いられます。
スコープ
ARG
による値定義が有効になるのは、Dockerfile
内の記述行以降です。
コマンドラインなどにおいて用いられるときではありません。
たとえば以下のような Dockerfile を見てみます。
1 FROM busybox
2 USER ${user:-some_user}
3 ARG user
4 USER $user
...
このファイルをビルドするには以下を実行します。
$ docker build --build-arg user=what_user .
2 行めの USER
が some-user
として評価されます。
これは user
変数が、直後の 3 行めにおいて定義されているからです。
そして 4 行めの USER
は what_user
として評価されます。
user
が定義済であって、コマンドラインから what_user
という値が受け渡されたからです。
ARG
命令による定義を行うまで、その変数を利用しても空の文字列として扱われます。
ARG
命令の変数スコープは、それが定義されたビルドステージが終了するときまでです。
複数のビルドステージにおいて ARG
を利用する場合は、個々に ARG
命令を指定する必要があります。
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS
ARG 変数の利用
ARG
命令や ENV
命令において変数を指定し、それを RUN
命令にて用いることができます。
ENV
命令を使って定義された環境変数は、ARG
命令において同名の変数が指定されていたとしても優先されます。
以下のように ENV
命令と ARG
命令を含む Dockerfile があるとします。
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER v1.0.0
4 RUN echo $CONT_IMG_VER
そしてこのイメージを以下のコマンドによりビルドしたとします。
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .
この例において RUN
命令は v1.0.0
という値を採用します。
コマンドラインから v2.0.1
が受け渡され ARG
の値に設定されますが、それが用いられるわけではありません。
これはちょうどシェルスクリプトにおいて行われる動きに似ています。
ローカルなスコープを持つ変数は、指定された引数や環境から受け継いだ変数よりも優先されます。
上の例を利用しつつ ENV
のもう 1 つ別の仕様を用いると、さらに ARG
と ENV
の組み合わせによる以下のような利用もできます。
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
4 RUN echo $CONT_IMG_VER
ARG
命令とは違って ENV
による値はビルドイメージ内に常に保持されます。
以下のような --build-arg
フラグのない docker build
を見てみます。
$ docker build .
上の Dockerfile の例を用いると、CONT_IMG_VER
の値はイメージ内に保持されますが、その値は v1.0.0
になります。
これは 3 行めの ENV
命令で設定されているデフォルト値です。
この例で見たように変数展開の手法では、コマンドラインから引数を受け渡すことが可能であり、ENV
命令を用いればその値を最終イメージに残すことができます。
変数展開は、特定の Dockerfile 命令においてのみサポートされます。
定義済 ARG 変数
Docker にはあらかじめ定義された ARG
変数があります。
これは Dockerfile において ARG
命令を指定しなくても利用することができます。
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy
これを利用する場合は、コマンドラインから以下のフラグを与えるだけです。
--build-arg <varname>=<value>
デフォルトにおいて、これらの定義済変数は docker history
による出力からは除外されます。
除外する理由は、HTTP_PROXY
などの各変数内にある重要な認証情報が漏洩するリスクを軽減するためです。
たとえば --build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com
という引数を用いて、以下の Dockerfile をビルドするとします。
FROM ubuntu
RUN echo "Hello World"
この場合、HTTP_PROXY
変数の値は docker history
から取得することはできず、キャッシュにも含まれていません。
したがって URL が変更され、プロキシーサーバーも http://user:pass@proxy.sfo.example.com
に変更したとしても、この後に続くビルド処理において、キャッシュミスは発生しません。
この動作を取り消す必要がある場合は、以下のように Dockerfile 内に ARG
命令を加えれば実現できます。
FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"
この Dockerfile がビルドされるとき、HTTP_PROXY
は docker history
に保存されます。
そしてその値を変更すると、ビルドキャッシュは無効化されます。
プラットフォームに応じて自動設定されるグローバル ARG 変数
この機能は BuildKit バックエンドを用いている場合にのみ利用可能です。
Docker にはあらかじめ定義された ARG
変数として、ビルド処理を行ったプラットフォーム(ビルドプラットフォーム)の、あるいはイメージを作り出したプラットフォーム(ターゲットプラットフォーム)の、各ノード情報を提供するものがあります。
ターゲットプラットフォームは docker build
の --platform
フラグを使って指定することもできます。
以下の ARG
変数が自動的に定義されています。
TARGETPLATFORM
- ビルド結果のプラットフォーム。linux/amd64
、linux/arm/v7
、windows/amd64
など。TARGETOS
- TARGETPLATFORM の OS 部分。TARGETARCH
- TARGETPLATFORM のアーキテクチャー部分。TARGETVARIANT
- TARGETPLATFORM のバリアント(variant)部分。BUILDPLATFORM
- ビルド処理を行ったプラットフォーム。BUILDOS
- BUILDPLATFORM の OS 部分。BUILDARCH
- BUILDPLATFORM のアーキテクチャー部分。BUILDVARIANT
- BUILDPLATFORM のバリアント(variant)部分。
この変数はグローバルスコープにより定義されます。
したがってビルドステージの内部にて、あるいは RUN
コマンドにおいて、自動的に利用できるものではありません。
ビルドステージにおいてこの変数を明示的に利用するためには、値をつけずにその変数を再定義します。
たとえば以下のとおりです。
FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
ビルドキャッシュへの影響
ARG
変数は ENV
変数とは違って、ビルドイメージの中に保持されません。
しかし ARG
変数はビルドキャッシュへ同じような影響を及ぼします。
Dockerfile に ARG
変数が定義されていて、その値が前回のビルドとは異なった値が設定されたとします。
このとき「キャッシュミス」(cache miss)が発生しますが、それは初めて利用されたときであり、定義された段階ではありません。
特に ARG
命令に続く RUN
命令は、ARG
変数の値を(環境変数として)暗に利用しますが、そこでキャッシュミスが起こります。
定義済の ARG
変数は、Dockerfile
内に ARG
行がない限りは、キャッシュは行われません。
たとえば以下の 2 つの Dockerfile を考えます。
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo $CONT_IMG_VER
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo hello
コマンドラインから --build-arg CONT_IMG_VER=<value>
を指定すると 2 つの例ともに、2 行めの記述ではキャッシュミスが起きず、3 行めで発生します。
ARG CONT_IMG_VER
は、RUN
行において CONT_IMG_VER=<value>
echo hello と同等のことが実行されるので、<value>
が変更されると、キャッシュミスが起こるということです。
もう 1 つの例を、同じコマンドライン実行を行って利用するとします。
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER $CONT_IMG_VER
4 RUN echo $CONT_IMG_VER
この例においてキャッシュミスは 3 行めで発生します。
これは ENV
における変数値が ARG
変数を参照しており、その変数値がコマンドラインから変更されるために起きます。
この例では ENV
コマンドがイメージに対して変数値を書き込むものとなります。
ENV
命令が ARG
命令の同一変数名を上書きする例を見てみます。
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER hello
4 RUN echo $CONT_IMG_VER
3 行めにおいてキャッシュミスは発生しません。
これは CONT_IMG_VER
が定数(hello
)であるからです。
その結果、4 行めの RUN
命令において用いられる環境変数およびその値は、ビルドの際に変更されません。
ONBUILD
ONBUILD [INSTRUCTION]
ONBUILD
命令は、イメージに対してトリガー命令(trigger instruction)を追加します。
トリガー命令は後々実行されるものであり、そのイメージが他のビルドにおけるベースイメージとして用いられたときに実行されます。
このトリガー命令は、後続のビルドコンテキスト内で実行されます。
後続の Dockerfile
内での FROM
命令の直後に、その命令が挿入されたかのようにして動作します。
どのようなビルド命令でも、トリガー命令として登録することができます。
この命令は、他のイメージのビルドに用いることを意図したイメージをビルドする際に利用できます。 たとえばアプリケーションやデーモンの開発環境であって、ユーザー特有の設定を行うような場合です。
たとえば、繰り返し利用できる Python アプリケーション環境イメージがあるとします。
そしてこのイメージにおいては、アプリケーションソースコードを所定のディレクトリに配置することが必要であって、さらにソースを配置した後にソースビルドを行うスクリプトを加えたいとします。
このままでは ADD
と RUN
を単に呼び出すだけでは実現できません。
それはアプリケーションソースコードがまだわかっていないからであり、ソースコードはアプリケーション環境ごとに異なるからです。
アプリケーション開発者に向けて、ひながたとなる Dockerfile
を提供して、コピーペーストした上でアプリケーションに組み入れるようにすることも考えられます。
しかしこれでは不十分であり、エラーも起こしやすくなります。
そしてアプリケーションに特有のコードが含まれることになるので、更新作業も大変になります。
これを解決するには ONBUILD
を利用します。
後々実行する追加の命令を登録しておき、次のビルドステージにおいて実行させるものです。
これは以下のようにして動作します。
ONBUILD
命令があると、現在ビルドしているイメージのメタデータに対してトリガーが追加されます。 この命令は現在のビルドには影響を与えません。- ビルドの最後に、トリガーの一覧がイメージマニフェスト内の
OnBuild
というキーのもとに保存されます。 この情報はdocker inspect
コマンドを使って確認することができます。 - 次のビルドにおけるベースイメージとして、このイメージを利用します。
その指定には
FROM
命令を用います。FROM
命令の処理の中で、後続ビルド処理がONBUILD
トリガーを見つけると、それが登録された順に実行していきます。 トリガーが 1 つでも失敗したら、FROM
命令は中断され、ビルドが失敗することになります。 すべてのトリガーが成功したらFROM
命令の処理が終わり、ビルド処理がその後に続きます。 - トリガーは、イメージが実行された後は、イメージ内から削除されます。 別の言い方をすれば、「孫」のビルドにまでは受け継がれないということです。
例として以下のようなことを追加する場合が考えられます。
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
警告:
ONBUILD
命令をつなぎ合わせた命令、ONBUILD ONBUILD
は実現することはできません。
警告:
ONBUILD
命令はFROM
命令やMAINTAINER
命令をトリガーとすることはできません。
STOPSIGNAL
STOPSIGNAL signal
STOPSIGNAL
命令はシステムコールシグナルを設定するものであり、コンテナーが終了するときに送信されます。
シグナルは負ではない整数値であり、カーネルのシステムコールテーブル内に合致するものを指定します。
たとえば 9 などです。
あるいは SIGNAME という形式のシグナル名を指定します。
たとえば SIGKILL などです。
HEALTHCHECK
HEALTHCHECK
命令には 2 つの書式があります。
HEALTHCHECK [OPTIONS] CMD command
(コンテナー内部でコマンドを実行し、コンテナーをヘルスチェック)HEALTHCHECK NONE
(ベースイメージが行うヘルスチェックを無効化)
HEALTHCHECK
命令は、コンテナーが動作していることをチェックする方法を指定するものです。
この機能はたとえば、ウェブサーバーのプロセスが稼動はしているものの、無限ループに陥っていて新たな接続を受け入れられない状態を検知する場合などに利用できます。
コンテナーにヘルスチェックが設定されていると、通常のステータスに加えてヘルスステータスを持つことになります。
このステータスの初期値は starting
です。
ヘルスチェックが行われると、このステータスは(それまでにどんなステータスであっても)healthy
となります。
ある一定数、連続してチェックに失敗すると、そのステータスは unhealty
となります。
CMD
より前に記述できるオプションは以下のものです。
--interval=DURATION
(デフォルト:30s
)--timeout=DURATION
(デフォルト:30s
)--start-period=DURATION
(デフォルト:0s
)--retries=N
(デフォルト:3
)
ヘルスチェックは、コンテナーが起動した interval 秒後に最初に起動されます。 そして直前のヘルスチェックが完了した interval 秒後に、再び実行されます。
1 回のヘルスチェックが timeout 秒以上かかったとき、そのチェックは失敗したものとして扱われます。
コンテナーに対するヘルスチェックが retries 回分、連続して失敗した場合は unhealthy
とみなされます。
開始時間 (start period)は、コンテナーが起動するまでに必要となる初期化時間を設定します。 この時間内にヘルスチェックの失敗が発生したとしても、 retries 数の最大を越えたかどうかの判断は行われません。 ただしこの開始時間内にヘルスチェックが 1 つでも成功したら、コンテナーは起動済であるとみなされます。 そこで、それ以降にヘルスチェックが失敗したら、retries 数の最大を越えたかどうかがカウントされます。
1 つの Dockerfile に記述できる HEALTHCHECK
命令はただ 1 つです。
複数の HEALTHCHECK
を記述しても、最後の命令しか効果はありません。
CMD
キーワードの後ろにあるコマンドは、シェルコマンド(たとえば HEALTHCHECK CMD /bin/check-running
)か、あるいは exec 形式の配列(他の Dockerfile コマンド、たとえば ENTRYPOINT
にあるもの)のいずれかを指定します。
そのコマンドの終了ステータスが、コンテナーのヘルスステータスを表わします。 返される値は以下となります。
- 0: 成功(success) - コンテナーは健康であり、利用が可能です。
- 1: 不健康(unhealthy) - コンテナーは正常に動作していません。
- 2: 予約(reserved) - このコードを戻り値として利用してはなりません。
たとえば 5 分間に 1 回のチェックとして、ウェブサーバーが 3 秒以内にサイトのメインページを提供できているかを確認するには、以下のようにします。
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
ヘルスチェックにが失敗しても、それをデバッグしやすくするために、そのコマンドが標準出力あるいは標準エラー出力へ書き込んだ文字列(UTF-8 エンコーディング)は、すべてヘルスステータス内に保存されます。
docker inspect
を使えば、すべて確認することができます。
ただしその出力は切り詰められます(現時点においては最初の 4096 バイト分のみを出力します)。
コンテナーのヘルスステータスが変更されると、health_status
イベントが生成されて、新たなヘルスステータスになります。
HEALTHCHECK
による機能は Docker 1.12 において追加されました。
SHELL
SHELL ["executable", "parameters"]
SHELL
命令は、各種コマンドのシェル形式において用いられるデフォルトのシェルを、上書き設定するために利用します。
デフォルトのシェルは Linux 上では ["/bin/sh", "-c"]
、Windows 上では ["cmd", "/S", "/C"]
です。
SHELL
命令は Dockerfile 内において JSON 形式で記述しなければなりません。
SHELL
命令は特に Windows 上において利用されます。
Windows には主に 2 つのネイティブなシェル、つまり cmd
と powershell
があり、両者はかなり異なります。
しかも 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
Dockerfile において RUN
、CMD
、ENTRYPOINT
の各コマンドをシェル形式で記述した際には、SHELL
命令の設定による影響が及びます。
以下に示す例は、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
命令とシェル形式を使って、Windows ユーザーにとって、より自然な文法で実現するやり方です。
特にパーサーディレクティブ escape
を組み合わせて実現します。
# 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:\docker\build\shell> 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:\docker\build\shell>
SHELL
命令はまた、シェルの動作を変更する際にも利用することができます。
たとえば Windows 上において SHELL cmd /S /C /V:ON|OFF
を実行すると、遅延環境変数の展開方法を変更することができます。
SHELL
命令は Linux において、zsh
、csh
、tcsh
などのシェルが必要となる場合にも利用することができます。
SHELL
による機能は Docker 1.12 において追加されました。
外部実装機能
この機能は BuildKit バックエンドを用いている場合にのみ利用可能です。
Docker によるビルド処理においては、実験的な機能として、キャッシュマウント、ビルドシークレット、ssh フォワーディングなどをサポートしており、これらは文法ディレクティブを利用した外部実装機能を通じて実現しています。 この機能に関する詳細は、BuildKit リポジトリ内のドキュメントを参照してください。
Dockerfile 記述例
以下では Dockerfile の文法例をいくつか示します。
# Nginx
#
# VERSION 0.0.1
FROM ubuntu
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
# Firefox over VNC
#
# VERSION 0.3
FROM ubuntu
# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'
EXPOSE 5900
CMD ["x11vnc", "-forever", "-usepw", "-create"]
# Multiple images example
#
# VERSION 0.1
FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f
FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4
# You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink.