Dockerfile の CMD と ENTRYPOINT
ちょっとわかりづらかったのでまとめてみる。基本的には公式ドキュメントを参照する。
CMD
3つの形式がある
exec 形式, 推奨 CMD ["executable", "param1", "param2"]
shell 形式 CMD ommand param1 param2
CMD instruction は、Dockerfile に対して 1 つのみ
複数記述すると、最後の CMD が有効になる
CMD の目的は、コンテナ実行時のデフォルトの挙動を提供する こと
exec 形式は、RUN の場合と同じでシェル自体は呼び出さない
ENTRYPOINT
コンテナが実行された時に 最初に実行される コマンドとそのパラメータを定義する。指定されたコマンドは、コンテナ内で PID 1 で実行される。 docker run --entrypoint で定義の上書きが可能。
2 つの形式がある。
exec 形式, (推奨) ENTRYPOINT ["executable", "param1", "param2"]
shell 形式 ENTRYPOINT command param1 param2
exec 形式
JSON array としてコマンドとその引数を定義する。実行バイナリを直指定すると、コマンドシェルを利用しない。
code:Dockerfile
そのため、シェルプロセスが必要な場合 (例えば、環境変数の展開など) は、シェルコマンドを明示的に指定する必要がある。一方で、実行可能なシェルがベースイメージに含まれていなくてもバイナリを実行できるという利点もある。
code:Dockefile
専用のプロセス起動用スクリプトを用意するという手段もある。signal を受け取れるようにするために、exec が最後に実行されるようにする。 code:Dockerfile
COPY ./docker-entrypoint.sh /
code:bash
set -e
chown -R postgres "$PGDATA"
gosu postgres initdb
fi
exec gosu postgres "$@"
fi
exec "$@"
shell 形式
以下のような特徴がある。
/bin/sh -c のサブコマンドとして認識される
docker run <image> の引数は無視される
CMD も無視される
実行するバイナリが PID 1 として起動されないため、signal を受け取れない。docker stop しても SIGTERM は直接受け取らず、タイムアウト後に SIGKILL が送信される。 ENTRYPOINT exec top -b とすれば、受け取れるから正常終了する
ENTRYPOINT top -b のように exec を忘れると、受け取れずに 10 秒のタイムアウトの後 SIGKILL される
以下のようなルールがあるようだ
Dockerfile は、少なくとも 1 つの CMD もしくは ENTRYPOINT が定義されていなければならない ENTRYPOINT は、コンテナを executable なものとして利用するなら指定しなければならない
CMD は、以下のどちらかのために利用すべき
ENTRYPOINT のデフォルトの引数を定義する
コンテナ内でアドホックなコマンドを実行する
CMD は、コンテナ実行時に他の引数が定義されていたら上書きされる
比較
両者が指定されていた場合、各々が shell 形式か exec 形式かでややこしいので、リファレンス内の表をみるのが早い。
試してみる
code:bash
$ docker build -t myimage .
$ docker run myimage
shell 形式の場合。
code:Dockerfile
FROM ubuntu
ENTRYPOINT /usr/bin/top -b, -c
docker run すると、以下のようになる。PID 1 は /bin/sh になっていることがわかる。この時、^C は受け付けず、docker stop してもすぐには終了できずに、数秒後に SIGKILL が送信され強制終了される。
code:sh
top - 04:15:40 up 2 days, 10:16, 0 users, load average: 0.00, 0.02, 0.00
Tasks: 2 total, 1 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.2 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 2046748 total, 114304 free, 470904 used, 1461540 buff/cache
KiB Swap: 1048572 total, 1047260 free, 1312 used. 1404844 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 4624 780 712 S 0.0 0.0 0:00.04 /bin/sh -c+
6 root 20 0 36480 3092 2740 R 0.0 0.2 0:00.00 /usr/bin/t+
一方、exec 形式の場合。
code:Dockerfile
FROM ubuntu
docker run すると、以下のようになる。PID 1 でプロセスが動いており、^C で終了もできるし、docker stop で正常終了もできる。
code:sh
top - 04:17:08 up 2 days, 10:18, 0 users, load average: 0.16, 0.04, 0.01
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.2 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 2046748 total, 109324 free, 475508 used, 1461916 buff/cache
KiB Swap: 1048572 total, 1047260 free, 1312 used. 1400164 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 36480 3112 2764 R 0.0 0.2 0:00.04 top -b -c
Node.js
init プロセス用のライブラリを入れれば良いという話だが、最近のバージョンでは tini が同梱されているので --init オプションを利用するだけで良いようだ。
Node.js でインストール時にエラーになったやつ