読む時間の目安: 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 ファイルの生成方法を参照してください。

慣例として DockerfileDockerfile と命名されています。 またこのファイルはコンテキストディレクトリのトップに置かれます。 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 内の命令を複数行に分けることができます。 Dockerfileescape パーサーディレクティブを記述していたとしても、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>

環境変数の置換

DockerfileENV 構文により宣言される環境変数は、特定の命令において変数として解釈されます。 エスケープについても構文内にリテラルを含めることから、変数と同様の扱いと考えられます。

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

この結果、defhello になります。 bye ではありません。 しかし ghibye になります。 ghi を設定している行は、abcbye を設定している命令と同一箇所ではないからです。

.dockerignore ファイル

Docker の CLI によってコンテキストが Docker デーモンに送信される前には、コンテキストのルートディレクトリの .dockerignore というファイルが参照されます。 このファイルが存在したら、CLI はそこに記述されたパターンにマッチするようなファイルやディレクトリを除外した上で、コンテキストを扱います。 必要もないのに、巨大なファイルや取り扱い注意のファイルを不用意に送信してしまうことが避けられ、ADDCOPY を使ってイメージに間違って送信してしまうことを防ぐことができます。

CLI は .dockerignore ファイルを各行ごとに区切られた設定一覧として捉えます。 ちょうど Unix シェルにおけるファイルグロブ(glob)と同様です。 マッチング処理の都合上、コンテキストのルートは、ワーキングディレクトリとルートディレクトリの双方であるものとしてみなされます。 たとえばパターンとして /foo/barfoo/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 命令は、イメージビルドのための処理ステージを初期化し、ベースイメージを設定します。後続の命令がこれに続きます。 このため、正しい DockerfileFROM 命令から始めなければなりません。 ベースイメージは正しいものであれば何でも構いません。 簡単に取り掛かりたいときは、公開リポジトリからイメージを取得します。

  • Dockerfile 内にて ARG は、FROM よりも前に記述できる唯一の命令です。 ARG と FROM の関連についてを参照してください。

  • 1 つの Dockerfile 内に FROM を複数記述することが可能です。 これは複数のイメージを生成するため、あるいは 1 つのビルドステージを使って依存イメージをビルドするために行います。 各 FROM 命令までのコミットによって出力される最終のイメージ ID は書き留めておいてください。 個々の FROM 命令は、それ以前の命令により作り出された状態を何も変更しません。

  • オプションとして、新たなビルドステージに対しては名前をつけることができます。 これは FROM 命令の AS name により行います。 この名前は後続の FROMCOPY --from=<name|index> 命令において利用することができ、このビルドステージにおいてビルドされたイメージを参照します。

  • tagdigest の設定はオプションです。 これを省略した場合、デフォルトである 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 命令を用いる場合、CMDENTRYPOINT の両命令とも、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 命令に指定されたデフォルトを上書きすることができます。

メモ: RUNCMD を混同しないようにしてください。 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 wgetRUN 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 つのいずれかに従います。

    1. コピー先に指定されていれば、それが存在しているかどうかに関わらず。あるいは、
    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 の内容を上書きすることができます。

シェル形式では CMDrun によるコマンドライン引数は受け付けずに処理を行います。 ただし 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 execdocker 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 の中で実行されます。 この形式は、シェル環境変数を置換しながらシェル処理を実行します。 そして 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

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 命令も、ともにコンテナー起動時に実行するコマンドを定義するものです。 両方が動作する際に必要となるルールがいくらかあります。

  1. Dockerfile には、CMD または ENTRYPOINT のいずれかが、少なくとも 1 つ必要です。

  2. ENTRYPOINT は、コンテナーを実行モジュールとして実行する際に利用します。

  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_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/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 配列として扱われます。 したがって文字列をくくるのはダブルクォート(")であり、シングルクォート(')は用いてはなりません。

  • コンテナー実行時に宣言されるホストディレクトリ: ホストディレクトリ(マウントポイント)は、その性質からして、ホストに依存するものです。 これはイメージの可搬性を確保するためなので、設定されたホストディレクトリが、あらゆるホスト上にて利用可能になるかどうかの保証はありません。 このため、Dockerfile の内部からホストディレクトリをマウントすることはできません。 つまり VOLUME 命令は host-dir(ホストのディレクトリを指定する)パラメーターをサポートしていません。 マウントポイントの指定は、コンテナーを生成、実行するときに行う必要があります。

USER

USER <user>[:<group>] または
USER <UID>[:<GID>]

USER 命令は、ユーザー名(または UID)と、オプションとしてユーザーグループ(または GID)を指定します。 そしてイメージが実行されるとき、Dockerfile 内の後続の RUNCMDENTRYPOINT の各命令においてこの情報を利用します。

警告: ユーザーにプライマリーグループがない場合、イメージ(あるいは次の命令)は 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 内にてその後に続く RUNCMDENTRYPOINTCOPYADD の各命令において利用することができます。 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 行めの USERsome-user として評価されます。 これは user 変数が、直後の 3 行めにおいて定義されているからです。 そして 4 行めの USERwhat_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 つ別の仕様を用いると、さらに ARGENV の組み合わせによる以下のような利用もできます。

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_PROXYdocker history に保存されます。 そしてその値を変更すると、ビルドキャッシュは無効化されます。

プラットフォームに応じて自動設定されるグローバル ARG 変数

この機能は BuildKit バックエンドを用いている場合にのみ利用可能です。

Docker にはあらかじめ定義された ARG 変数として、ビルド処理を行ったプラットフォーム(ビルドプラットフォーム)の、あるいはイメージを作り出したプラットフォーム(ターゲットプラットフォーム)の、各ノード情報を提供するものがあります。 ターゲットプラットフォームは docker build--platform フラグを使って指定することもできます。

以下の ARG 変数が自動的に定義されています。

  • TARGETPLATFORM - ビルド結果のプラットフォーム。 linux/amd64linux/arm/v7windows/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 アプリケーション環境イメージがあるとします。 そしてこのイメージにおいては、アプリケーションソースコードを所定のディレクトリに配置することが必要であって、さらにソースを配置した後にソースビルドを行うスクリプトを加えたいとします。 このままでは ADDRUN を単に呼び出すだけでは実現できません。 それはアプリケーションソースコードがまだわかっていないからであり、ソースコードはアプリケーション環境ごとに異なるからです。 アプリケーション開発者に向けて、ひながたとなる Dockerfile を提供して、コピーペーストした上でアプリケーションに組み入れるようにすることも考えられます。 しかしこれでは不十分であり、エラーも起こしやすくなります。 そしてアプリケーションに特有のコードが含まれることになるので、更新作業も大変になります。

これを解決するには ONBUILD を利用します。 後々実行する追加の命令を登録しておき、次のビルドステージにおいて実行させるものです。

これは以下のようにして動作します。

  1. ONBUILD 命令があると、現在ビルドしているイメージのメタデータに対してトリガーが追加されます。 この命令は現在のビルドには影響を与えません。
  2. ビルドの最後に、トリガーの一覧がイメージマニフェスト内の OnBuild というキーのもとに保存されます。 この情報は docker inspect コマンドを使って確認することができます。
  3. 次のビルドにおけるベースイメージとして、このイメージを利用します。 その指定には FROM 命令を用います。 FROM 命令の処理の中で、後続ビルド処理が ONBUILD トリガーを見つけると、それが登録された順に実行していきます。 トリガーが 1 つでも失敗したら、FROM 命令は中断され、ビルドが失敗することになります。 すべてのトリガーが成功したら FROM 命令の処理が終わり、ビルド処理がその後に続きます。
  4. トリガーは、イメージが実行された後は、イメージ内から削除されます。 別の言い方をすれば、「孫」のビルドにまでは受け継がれないということです。

例として以下のようなことを追加する場合が考えられます。

[...]
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 つのネイティブなシェル、つまり cmdpowershell があり、両者はかなり異なります。 しかも 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 において RUNCMDENTRYPOINT の各コマンドをシェル形式で記述した際には、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 において、zshcshtcsh などのシェルが必要となる場合にも利用することができます。

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.
builder, docker, Dockerfile, automation, image creation