メインコンテンツまでスキップ

Dockerfile リファレンス

Docker は Dockerfile からの指示を読み取ることによって自動的にイメージをビルドすることができます。Dockerfile は、ユーザーがコマンドラインで呼び出すことができるすべてのコマンドを含むテキストドキュメントです。このページでは、Dockerfile で使用できるコマンドについて説明します。

概要

Dockerfile は以下の指示をサポートしています:

指示説明
ADDローカルまたはリモートのファイルおよびディレクトリを追加します。
ARGビルド時の変数を使用します。
CMDデフォルトコマンドを指定します。
COPYファイルおよびディレクトリをコピーします。
ENTRYPOINTデフォルト実行可能ファイルを指定します。
ENV環境変数を設定します。
EXPOSEアプリケーションがリッスンしているポートを説明します。
FROMベースイメージから新しいビルドステージを作成します。
HEALTHCHECK起動時にコンテナの健康をチェックします。
LABELイメージにメタデータを追加します。
MAINTAINERイメージの作者を指定します。
ONBUILDイメージがビルドで使用される際の指示を指定します。
RUNビルドコマンドを実行します。
SHELLイメージのデフォルトシェルを設定します。
STOPSIGNALコンテナを終了するためのシステムコールシグナルを指定します。
USERユーザIDおよびグループIDを設定します。
VOLUMEボリュームマウントを作成します。
WORKDIR作業ディレクトリを変更します。

ADD

ADD 指示と COPY は基本的に同じ形式と性質を持っています。しかし、ADDCOPY の上にいくつかの追加機能を加えます。

例えば、<source path>URL である場合、Docker エンジンはこのリンクからファイルをダウンロードして <target path> に配置しようとします。ダウンロードされたファイルの権限は自動的に 600 に設定されます。これが望ましい権限でない場合は、権限を調整するために追加の RUN レイヤーが必要になります。さらに、ダウンロードされたファイルが圧縮アーカイブの場合、抽出するために追加の RUN レイヤーも必要です。したがって、RUN 指示を直接使用し、wget または curl ツールを使用してダウンロードし、権限を処理し、抽出し、不要なファイルをクリーンアップする方が合理的です。このような機能は実際には実用的ではないため、使用することはお勧めできません。

<source path>tar 圧縮ファイルであり、圧縮形式が gzipbzip2、または xz の場合、ADD 指示はこの圧縮ファイルを自動的に <target path> に解凍します。

いくつかのケースでは、公式の ubuntu イメージのように、この自動解凍機能が非常に便利です:

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...

しかし、圧縮ファイルを解凍せずにコピーしたいと本当に思っている場合、ADD コマンドを使用することはできません。

Docker 公式の Dockerfile ベストプラクティスドキュメントによると、可能な限り COPY を使用する必要があります。COPY の意味は非常にクリアであり、単にファイルをコピーするだけですが、ADD にはより複雑な機能が含まれており、その振る舞いがクリアでない場合があります。ADD に最適な使用ケースは、前述の自動解凍が必要な場合です。

さらに、ADD 指示はイメージビルドキャッシュを無効にする原因となることが注意されるべきであり、これによってイメージビルドプロセスが遅くなる可能性があります。

したがって、COPYADD の指示を選ぶ際には、この原則に従うことができます:すべてのファイルコピー操作に COPY 指示を使用し、自動解凍が必要な場合のみ ADD を使用してください。

この指示を使用する際には、--chown=<user>:<group> オプションを追加して、ファイルの所有者ユーザーとグループを変更することもできます。

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

ARG

フォーマット: ARG <parameter_name>[=<default_value>]

ARG 命令は、ENV と同じ効果を持ちますが、両者とも環境変数を設定します。違いは、ARG によってビルド環境で設定された環境変数は、コンテナが実行されているときには存在しないという点です。ただし、この理由だけで ARG をパスワードやその他の機密情報を保存するために使用しないでください。なぜなら、docker history はすべての値を表示できるためです。

Dockerfile の ARG 命令は、パラメータ名とそのデフォルト値を定義します。このデフォルト値は、docker build コマンドで --build-arg <parameter_name>=<value> としてオーバーライドできます。

ARG 命令を柔軟に使用することで、Dockerfile を変更せずに異なるイメージをビルドできます。

ARG 命令には効果のスコープがあります。FROM 命令の前に指定された場合、FROM 命令でのみ使用できます。

ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo ${DOCKER_USERNAME}

上記の Dockerfile を使用すると、${DOCKER_USERNAME} 変数の値は出力されません。正しく出力するには、FROM の後で再度 ARG を指定する必要があります。

# FROM のみで有効
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# FROM の後で使用するには、再度指定する必要があります
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

マルチステージビルドの場合、この問題に特に注意してください。

# この変数は各 FROM で有効です
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 1

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 2

上記の Dockerfile では、両方の FROM 命令で ${DOCKER_USERNAME} を使用できます。各ステージで使用される変数は、各ステージで個別に指定する必要があります。

ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# FROM の後で変数を使用するには、各ステージで個別に指定する必要があります
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

FROM ${DOCKER_USERNAME}/alpine

# FROM の後で変数を使用するには、各ステージで個別に指定する必要があります
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

CMD

CMD 命令には RUN と似た形式があり、次の2つの形式があります。

  • shell 形式: CMD <command>
  • exec 形式: CMD ["executable", "param1", "param2"...]
  • パラメータリスト形式: CMD ["param1", "param2"...]ENTRYPOINT 命令が指定された後に実際のパラメータを指定するために使用されます。

以前コンテナについて紹介した時、Dockerは仮想マシンではなく、コンテナはプロセスであると言及しました。それらはプロセスなので、コンテナを起動する際にはプログラムとそのパラメータを指定する必要があります。CMD 命令は、コンテナのメインプロセスのデフォルトの起動コマンドを指定するために使用されます。

実行時には、イメージで設定されたこのデフォルトコマンドを新しいコマンドで置き換えることができます。例えば、ubuntu イメージのデフォルトのCMD/bin/bash です。もし私たちが直接 docker run -it ubuntu を実行すると、bash に入ります。また、実行時に異なるコマンドを指定することもできます。例えば docker run -it ubuntu cat /etc/os-release とすると、デフォルトの /bin/bash コマンドを cat /etc/os-release に置き換えて、システムのバージョン情報を出力します。

命令形式については、一般的に exec 形式が推奨されます。これは解析時にJSON配列として解析されるため、ダブルクォート " を使用する必要があり、シングルクォートは使用できません。

shell 形式を使用する場合、実際のコマンドは sh -c の引数としてラップされて実行されます。例えば:

CMD echo $HOME

実際の実行時には、以下のようになります:

CMD [ "sh", "-c", "echo $HOME" ]

これは、これらの環境変数がシェルによって解析され、処理されるために、環境変数を使用できる理由です。

CMD について言及すると、コンテナ内のアプリケーションがフォアグラウンドで実行されるべきか、バックグラウンドで実行されるべきかという問題が避けられません。これは初心者にとって一般的な混乱です。

Dockerは仮想マシンではなく、コンテナ内のアプリケーションはフォアグラウンドで実行されるべきです。仮想マシンや物理マシンでは systemd を使用してバックグラウンドサービスを起動するのとは異なり、コンテナの中にはバックグラウンドサービスの概念はありません。

いくつかの初心者は CMD を以下のように書きます:

CMD service nginx start

そして、実行後すぐにコンテナが終了することに気づきます。コンテナ内で systemctl コマンドを使用しても、全く実行できないことがわかります。これは、フォアグラウンドとバックグラウンドの概念を理解しておらず、コンテナと仮想マシンの違いを区別せず、コンテナを従来の仮想マシンの視点から理解しようとしているためです。

コンテナのスタートアッププログラムはコンテナアプリケーションプロセスです。コンテナはメインプロセスのために存在し、メインプロセスが終了すると、コンテナも目的を失い、同様に終了します。他の補助プロセスは、気にする必要のないものです。

service nginx start コマンドを使用することは、initシステムがnginxサービスをバックグラウンドデーモンプロセスとして起動しようとする試みです。先に述べたように、CMD service nginx startCMD [ "sh", "-c", "service nginx start"] と解釈されるため、実際のメインプロセスは sh です。service nginx start コマンドが終了すると、sh も終了し、メインプロセスが終了し、自然とコンテナが終了します。

正しいアプローチは、直接 nginx 実行ファイルを実行し、それをフォアグラウンドで実行することを要求することです。例えば:

CMD ["nginx", "-g", "daemon off;"]
COPY [--chown=<user>:<group>] <source_path>... <destination_path>
COPY [--chown=<user>:<group>] ["<source_path1>",... "<destination_path>"]

コマンドラインに似たフォーマットと、関数呼び出しに似たフォーマットの二つがあり、`RUN`命令と同様です。

`COPY`命令は、ビルドコンテキスト内の`<source_path>`からファイルやディレクトリを新しいイメージのレイヤーの`<destination_path>`へコピーします。例えば:

```bash
COPY package.json /usr/src/app/

<source_path>は複数のパスにすることができ、ワイルドカードを使用することもできます。ワイルドカードルールはGoのfilepath.Matchルールに準拠する必要があります。例えば:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<destination_path>はコンテナ内の絶対パスか、作業ディレクトリに対する相対パスにすることができます(作業ディレクトリはWORKDIR命令を使用して指定できます)。コピー先のパスは事前に作成する必要はありません。ディレクトリが存在しない場合は、ファイルをコピーする前に作成されます。

加えて、COPY命令を使用する際には、ソースファイルの様々なメタデータが保持されることに注意が必要です。例えば、読み取り、書き込み、実行権限やファイルの変更時間などです。この特徴はイメージのカスタマイズに役立ちます。特に、関連するファイルをGitで管理している場合に便利です。

この命令を使用する際には、--chown=<user>:<group>オプションを追加して、ファイルの所有者とグループを変更することもできます。

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

ソースパスがフォルダーの場合、コピーする際に直接フォルダーをコピーするのではなく、フォルダーの内容を目的のパスにコピーします。

ENTRYPOINT

ENTRYPOINTのフォーマットは、RUN命令と同じで、execフォーマットとshellフォーマットに分けられます。

ENTRYPOINTの目的はCMDと同じで、どちらもコンテナの起動プログラムとそのパラメータを指定するために使用されます。ENTRYPOINTも実行時に置き換えることができますが、CMDよりも少し手間がかかり、docker run--entrypointパラメータを使用して指定する必要があります。

ENTRYPOINTが指定されると、CMDの意味が変わります。それはもはや直接そのコマンドを実行するのではなく、ENTRYPOINT命令に渡される引数としてCMDの内容を扱います。言い換えれば、実行される際にはこのようになります:

<ENTRYPOINT> "<CMD>"

では、CMDがあるなら、なぜENTRYPOINTが必要なのでしょうか?この<ENTRYPOINT> "<CMD>"にはどのような利点があるのでしょうか?いくつかのシナリオを見てみましょう。

シナリオ1: イメージがコマンドのように振る舞うようにする

例えば、私たちの現在のパブリックIPアドレスを知るイメージが必要だとしましょう。まずCMDを使用して実装することができます:

FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://myip.ipip.net" ]

もしdocker build -t myip .を使用してイメージをビルドし、現在のパブリックIPをクエリする必要がある場合、次のように実行するだけです:

$ docker run myip
現在のIP:8.8.8.8 カリフォルニア州アメリカ合衆国

さて、今ではコマンドとしてイメージを使用できるように思えますが、通常コマンドには引数があります。引数を追加したい場合はどうでしょうか?上記のCMDからわかるように、実際のコマンドはcurlですので、HTTPヘッダ情報を表示するためには-iパラメータを追加する必要があります。docker run myip -iに直接-iパラメータを追加できますか?

$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".

実行可能なファイルが見つからないというエラーが表示されていますが、先に述べたように、イメージ名の後に続くものはcommandであり、実行時にデフォルトのCMDの値を置き換えることになります。したがって、ここでは-iが元のCMDを置き換えてしまい、元のcurl -s http://myip.ipip.netの最後に追加されるのではありません。しかし、-iはコマンドではないので、当然見つかることはありません。

したがって、-iパラメータを追加する場合は、完全なコマンドを再入力する必要があります:

$ docker run myip curl -s http://myip.ipip.net -i

これは明らかに良い解決策ではありませんが、ENTRYPOINTを使用するとこの問題を解決できます。今度はENTRYPOINTを使用してこのイメージを再実装してみましょう:

FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]

今度はdocker run myip -iを再度試してみましょう:

$ docker run myip
現在のIP:8.8.8.8 カリフォルニア州アメリカ合衆国

$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive

現在のIP:8.8.8.8 カリフォルニア州アメリカ合衆国

今回はうまくいきました。これはENTRYPOINTが存在する場合、CMDの内容がENTRYPOINTへの引数として渡されるからであり、ここでは-iが新しいCMDであるため、curlの引数として渡され、望んだ効果を得ることができました。

シナリオ2: アプリケーション起動前の準備作業

コンテナを起動するとは、メインプロセスを開始することですが、メインプロセスを開始する前に、いくつかの準備作業が必要な場合があります。

たとえば、mysqlのようなデータベースタイプでは、いくつかのデータベース設定や初期化作業が必要になることがあり、これらのタスクは最終的なmysqlサーバーが実行される前に解決する必要があります。

さらに、rootユーザーとしてサービスを開始することは避けたいですが、サービスの開始前には、それでもrootユーザーとして必要な準備作業を実行し、最終的にはサービスユーザーのアイデンティティに切り替えてサービスを開始する必要があります。または、デバッグのような便利性のために、サービス以外のコマンドをrootユーザーとして実行することができます。

これらの準備作業はコンテナのCMDとは関係がなく、CMDが何であろうと関わらず事前処理ステップとして実行されなければなりません。この場合、スクリプトを書いてENTRYPOINTに置いて実行することができます。このスクリプトは受け取った引数(つまり<CMD>)を最後に実行するコマンドとして扱います。例えば、公式のredisイメージはこのようにしています:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]

redisサービス用にredisユーザーを作成し、最終的にENTRYPOINTdocker-entrypoint.shスクリプトとして指定しています。

#!/bin/sh
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
find . \! -user redis -exec chown redis '{}' +
exec gosu redis "$0" "$@"
fi

exec "$@"

このスクリプトの内容はCMDの内容に基づいて判断しています。redis-serverであれば、redisユーザーのアイデンティティに切り替えてサーバーを開始します。それ以外の場合は、rootユーザーとして実行されます。例えば:

$ docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)

ENV

ENVは環境変数を設定するための命令で、以下の2つのフォーマットがあります。

  • ENV <key> <value>
  • ENV <key1>=<value1> <key2>=<value2>...

この命令は非常にシンプルで、環境変数を設定するだけです。以降のRUN命令や実行時に実行されるアプリケーションは、ここで定義された環境変数を直接使用することができます。

ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"

この例では、改行の方法と、スペースが含まれる値をダブルクォートで囲む方法を示しており、シェルの振る舞いと一致しています。

一度環境変数が定義されると、後続の命令で使用することができます。例えば、公式のnodeイメージのDockerfileには以下のようなコードがあります。

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs

環境変数は以下の命令で展開できます:ADD, COPY, ENV, EXPOSE, FROM, LABEL, USER, WORKDIR, VOLUME, STOPSIGNAL, ONBUILD, RUN。

この命令リストからわかるように、環境変数は多くの場面で使用することができ、非常に強力です。環境変数を利用することで、異なる環境変数を使って一つのDockerfileから複数のイメージを作成することができます。

EXPOSE

フォーマットはEXPOSE <port1> [<port2>...]です。

EXPOSE命令は、コンテナが実行中に提供するサービスのポートを宣言します。これは単なる宣言であり、この宣言のためにアプリケーションが実際にこれらのポートを開くわけではありません。Dockerfileにこの宣言を書くことのメリットは2つあります:一つは、このイメージサービスのデーモンポートをイメージユーザーに理解させるのに便利であり、ポートマッピングの設定に役立ちます。もう一つは、実行時にランダムポートマッピング、すなわちdocker run -Pを使用すると、EXPOSEによって公開されるポートが自動的にランダムにマッピングされることです。

EXPOSE命令は、実行時に-p <host port>:<container port>を使用することと区別する必要があります。-pはホストポートとコンテナポートをマッピングし、すなわち、コンテナの対応するポートサービスを外部アクセスに公開しますが、EXPOSEはコンテナが使用する意図のポートを宣言するだけであり、ホスト上で自動的にポートマッピングを実行するわけではありません。

FROM

Dockerfileでは、FROM命令はベースイメージを指定し、これが新しいイメージを構築するための出発点です。これはDockerfileの最初の命令であり、ビルドプロセスのためのベース環境を定義するために使用されます。

FROM命令の一般的な使用方法

  1. 公式イメージから構築する:

    FROM image:tag

    この使用法は、既存の公式イメージをベースイメージとして指定します。imageはイメージの名前であり、tagはバージョンタグです。例えば、ベースイメージとしてubuntu:18.04を使用することができます。

  2. カスタムイメージから構築する:

    FROM <username>/<imagename>:<tag>

    この使用法は、カスタムイメージをベースイメージとして指定します。<username>はDocker Hub上のユーザー名、<imagename>はイメージの名前、<tag>はバージョンタグです。

  3. マルチステージビルド:

    FROM <base-image> AS <stage-name>

    この使用法では、単一のDockerfileで複数のビルドステージを定義し、各ステージに異なるベースイメージを使用できます。<base-image>はベースイメージであり、<stage-name>はステージの名前です。

    マルチステージビルドは通常、ビルドプロセス中に異なるツールや依存関係を利用し、その後必要なファイルや実行可能ファイルを一つのステージから次のステージにコピーし、最終的なイメージのサイズを減らすために使用されます。

  4. ローカルファイルシステムから構築する:

    FROM scratch

    この使用法は、既存のベースイメージを使用せず、空のイメージからビルドを開始することを示します。この場合、必要なファイルや設定を自分で追加する必要があります。

FROM命令はDockerfile内で1回しか現れることができず、最初の命令でなければなりません。これはビルドプロセスの出発点を定義し、後続の命令はこの出発点に基づいて行われます。

HEALTHCHECK

形式:

  • HEALTHCHECK [オプション] CMD <コマンド>: コンテナのヘルスチェック状態を確認するコマンドを設定します。
  • HEALTHCHECK NONE: ベースイメージにヘルスチェックの命令が含まれている場合、この行を使用してベースイメージのヘルスチェック命令を上書きします。

HEALTHCHECK命令はDockerにコンテナの状態が正常かどうかを判断する方法を指示します。これはDocker 1.12で導入された新しい命令です。

HEALTHCHECK命令の前は、Dockerエンジンはコンテナ内のメインプロセスが終了したかどうかを確認することで、コンテナが異常状態にあるかどうかを判断していました。多くの場合、これは問題ではありませんが、プログラムがデッドロックや無限ループの状態に入ると、アプリケーションプロセスは終了しませんが、コンテナはサービスを提供できなくなります。1.12より前は、Dockerはコンテナ内のこのような状態を検出せず、リスケジュールを行わないため、サービスを提供できないがユーザーのリクエストを受け入れているコンテナが発生することがありました。

1.12から、DockerはHEALTHCHECK命令を提供し、メインプロセスのサービス状態がまだ正常であるかどうかを判断するコマンドを指定し、コンテナの実際の状態をより正確に反映できるようになりました。

イメージにHEALTHCHECK命令が指定されていて、それからコンテナが開始された場合、初期状態はstartingになります。HEALTHCHECK命令が通過すると、状態はhealthyに変わります。チェックが一定回数連続して失敗すると、状態はunhealthyに変わります。

HEALTHCHECKは以下のオプションをサポートしています:

  • --interval=<間隔>: 2回のヘルスチェック間の間隔で、デフォルトは30秒です。
  • --timeout=<期間>: ヘルスチェックコマンドを実行するタイムアウト時間です。この時間を超えると、現在のヘルスチェックは失敗とみなされ、デフォルトは30秒です。
  • --retries=<回数>: 指定された回数連続して失敗した後、コンテナの状態はunhealthyとみなされ、デフォルトは3回です。

CMDENTRYPOINTと同様に、HEALTHCHECKは一度だけ出現可能です。複数記述された場合は、最後のものが有効になります。

HEALTHCHECK [オプション] CMDの後のコマンドはENTRYPOINTと同じ形式であり、shellフォーマットとexecフォーマットがあります。コマンドの戻り値はヘルスチェックの成功または失敗を決定します:0:成功; 1:失敗; 2:予約済み、この値は使用しないでください。

例えば、シンプルなWebサービスのイメージがあり、そのWebサービスが適切に動作しているかを判断するためのヘルスチェックを追加したいとします。この決定にはcurlを使用することができます。Dockerfile内のHEALTHCHECKは以下のように書くことができます:

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1

ここでは、チェックを5秒ごとに実行するように設定しています(この間隔はテスト目的で非常に短く、実際には比較的長いべきです)、また、ヘルスチェックコマンドが3秒以内に応答しない場合は失敗とみなします。ヘルスチェックコマンドとしてcurl -fs http://localhost/ || exit 1を使用します。

このイメージをビルドするにはdocker buildを使用します:

$ docker build -t myweb:v1 .

ビルド後、コンテナを起動します:

$ docker run -d --name web -p 80:80 myweb:v1

このイメージを実行した後、docker container lsを使用して初期状態が(health: starting)であることがわかります:

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web

数秒待って再びdocker container lsを実行すると、ヘルスステータスが(healthy)に変わっていることがわかります:

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web

ヘルスチェックがリトライの回数よりも連続して失敗すると、状態は(unhealthy)に変わります。

トラブルシューティングに役立てるため、ヘルスチェックコマンドの出力(stdoutstderrを含む)はヘルス状態に保存され、docker inspectを使って閲覧できます。

$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}

LABEL

LABEL命令は、イメージにキーと値のペアのメタデータを追加するために使用されます。

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

イメージの作者、ドキュメントのアドレスなどを宣言するためにいくつかのラベルを使用することもできます:

LABEL org.opencontainers.image.authors="yeasy"

LABEL org.opencontainers.image.documentation="https://www.ubitools.com"

RUN

DockerfileでのRUN命令は、コンテナ内部でコマンドを実行するために使用されます。有効なコマンドやシェルスクリプトを実行することができます。

RUN命令の一般的な使用例

  1. 単一のコマンドを実行する:

    RUN <command>

    この使用法では、<command>はコンテナ内で実行される単一のコマンドです。例えば:

    RUN apt-get update
    RUN apt-get install -y package

    これは、それぞれパッケージリストを更新し、パッケージをインストールするコマンドをコンテナ内で実行します。

  2. 複数のコマンドを実行する:

    RUN <command1> && <command2>

    この使用法では、&&オペレータを使用して一行に連続して複数のコマンドを実行し、次のコマンドを実行する前に各コマンドが成功することを保証します。例えば:

    RUN apt-get update && apt-get install -y package

    これは、パッケージリストを更新し、パッケージを順番にインストールするコマンドをコンテナ内で実行し、前のコマンドが成功することを保証しながら次のコマンドを実行します。

  3. シェルスクリプトを実行する:

    RUN /bin/bash -c "<script>"

    この使用法では、コンテナ内で複雑なシェルスクリプトを実行することができます。スクリプトはダブルクォーテーション内に配置され、/bin/bash -cを使用してBashがスクリプトを解釈して実行することを指定します。 例えば:

    RUN /bin/bash -c "source setup.sh && build.sh"

    これは、setup.shスクリプトとbuild.shスクリプトをコンテナ内で実行します。

RUN命令は複数回使用することができ、それぞれの命令がコンテナ内でコマンドを実行します。各RUN命令は、前のものの上に新しいイメージレイヤーを作成します。

メモ:

  • RUN命令で実行されたコマンドは、コンテナに恒久的な影響を与えるため、不必要なファイルやデータをイメージに残さないように、クリーンアップコマンドが含まれている必要があります。
  • RUN命令で環境変数を使う必要がある場合、DockerfileでENV命令を使用して定義することができます。

SHELL

フォーマット: SHELL ["実行ファイル", "パラメータ"]

SHELL命令は、RUNENTRYPOINTCMD命令で使用するシェルを指定できます。Linuxのデフォルトは["/bin/sh", "-c"]です。

SHELL ["/bin/sh", "-c"]

RUN ls ; ls

SHELL ["/bin/sh", "-cex"]

RUN ls ; ls

上記の2つのRUN命令は同じコマンドを実行しますが、2番目のRUNは各コマンドを出力してエラーで終了します。

ENTRYPOINTCMDがシェル形式で指定されている場合、SHELL命令によって指定されたシェルもこれら2つの命令で使用するシェルになります。

SHELL ["/bin/sh", "-cex"]

# /bin/sh -cex "nginx"
ENTRYPOINT nginx
SHELL ["/bin/sh", "-cex"]

# /bin/sh -cex "nginx"
CMD nginx

STOPSIGNAL

STOPSIGNAL命令は、コンテナに停止信号を送信する際に使用するシステムコール信号を設定するために使用されます。この命令は信号値または対応する信号名を引数として受け取ります。

構文:

STOPSIGNAL signal

ここで:

  • signalは信号の数値または名前(例:SIGKILL)が使用できます。

STOPSIGNAL命令が指定されていない場合、docker stopコマンドを使用すると、SIGTERM信号が送信されます。指定された時間内にコンテナが停止しない場合、SIGKILL信号が送信されてコンテナを強制終了します。

例1:

FROM ubuntu:18.04
STOPSIGNAL SIGTERM
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]

上記の例では、停止信号としてSIGTERM信号を送信するように設定しています。docker stopコマンドが実行されると、コンテナはまずSIGTERM信号を受け取り、指定された時間内に正常に停止しなければSIGKILL信号が送信されてコンテナを終了します。

例2:

FROM ubuntu:18.04
STOPSIGNAL 9
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]

この例では、信号値9を使用してSIGKILL信号を送信することを指定しており、正常な停止を待たずに直接コンテナプロセスを終了します。

STOPSIGNALが設定されていても、コンテナが回復不可能な状態にあるなど、特定の状況でDockerは依然としてSIGKILL信号を送信してコンテナを終了させる可能性があります。

USER

フォーマット: USER <ユーザ名>[:<グループ>]

USER命令はWORKDIRと似ており、両方とも環境状態を変更し、後続のレイヤーに影響を与えます。WORKDIRは作業ディレクトリを変更するのに対し、USERは後続のRUNCMDENTRYPOINT命令を実行するユーザーIDを変更します。

USERは指定されたユーザーに切り替えるためのものですが、このユーザーは事前に作成されている必要があるため、作成されていない場合は切り替えることはできません。

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

rootとしてスクリプトを実行するが、実行中にIDを変更したい場合、たとえばサービスプロセスを事前に作成されたユーザーとして実行したい場合、suまたはsudoは使用しないでください。それらは複雑な設定が必要で、TTYのない環境ではしばしば失敗します。gosuの使用を推奨します。

# redisユーザーを作成し、gosuを使用して別のユーザーに切り替えてコマンドを実行します
RUN groupadd -r redis && useradd -r -g redis redis
# gosuをダウンロードします
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# CMDを設定し、他のユーザーとして実行します
CMD [ "exec", "gosu", "redis", "redis-server" ]

VOLUME

フォーマット:

  • VOLUME ["<パス1>", "<パス2>"...]
  • VOLUME <パス>

以前に述べたように、コンテナのストレージ層は、実行時に可能な限り読み取り専用に保つべきです。データベースなど、動的なデータを保存する必要があるアプリケーションの場合、そのデータベースファイルはボリュームに保存されるべきです。Dockerボリュームの概念については、後の章でさらに紹介します。実行時に動的データ用のディレクトリをボリュームとしてマウントするのをユーザが忘れないようにするために、Dockerfileで特定のディレクトリを匿名ボリュームとしてマウントするように指定することができます。この方法で、たとえユーザがマウントを指定しなくても、アプリケーションは正常に実行でき、コンテナのストレージ層に大量のデータを書き込むことはありません。

VOLUME /data

この例では、/dataディレクトリは、コンテナの実行時に自動的に匿名ボリュームとしてマウントされます。/dataに書き込まれたデータはコンテナのストレージ層に記録されず、コンテナのストレージ層の無状態の性質を保証します。もちろん、このマウント設定はコンテナを実行するときに上書きすることができます。例えば:

$ docker run -d -v mydata:/data xxxx

このコマンドでは、名前付きボリュームmydata/dataの位置にマウントされ、Dockerfileで定義された匿名ボリュームのマウント設定が上書きされます。

WORKDIR

フォーマット: WORKDIR <作業ディレクトリのパス>

WORKDIRインストラクションは、作業ディレクトリ(または現在のディレクトリ)を指定するために使用することができます。このインストラクションの後、後続のレイヤーに対する現在のディレクトリは指定されたディレクトリに設定されます。ディレクトリが存在しない場合、WORKDIRはそれを作成します。

以前、初心者がDockerfileをシェルスクリプトのように扱うという一般的な間違いについて触れました。この誤解は、次のエラーにもつながる可能性があります:

RUN cd /app
RUN echo "hello" > world.txt

このDockerfileからイメージをビルドして実行すると、/app/world.txtファイルが見つからない、またはその内容がhelloでないことがわかります。理由は明白です: シェルでは、連続した二行は同じプロセス実行環境の一部であり、以前のコマンドによって変更されたメモリ状態が次のコマンドに直接影響します。しかし、Dockerfileでは、これら二つのRUNコマンドは完全に異なるコンテナ環境で実行され、それぞれが全く別のコンテナです。これは、Dockerfileのビルドにおけるレイヤードストレージの概念を理解していないことによるエラーです。

前述のように、各RUNは新しいコンテナを起動し、コマンドを実行し、その後ファイル変更をコミットします。最初のレイヤーRUN cd /appは現在のプロセスの作業ディレクトリを変更するだけであり、これはメモリ内の変更であり、ファイルの変更にはなりません。二番目のレイヤーに到達すると、まったく新しいコンテナが起動され、最初のレイヤーのコンテナとは無関係なので、前のビルドプロセスのメモリ内の変更を継承することはできません。

そのため、後続のレイヤーのために作業ディレクトリの場所を変更する必要がある場合は、WORKDIRインストラクションを使用すべきです。

WORKDIR /app

RUN echo "hello" > world.txt

WORKDIRインストラクションで相対パスを使用する場合、切り替えられるディレクトリは前のWORKDIRに対して相対的です:

WORKDIR /a
WORKDIR b
WORKDIR c

RUN pwd

RUN pwdの作業ディレクトリは/a/b/cになります。