🐷

コピペで学ぶチュートリアル: DockerfileのCMDとENTRYPOINTの違い

2022/09/05に公開

Dockerfile の CMD と ENTRYPOINT について、手を動かして動作確認できるチュートリアルがあると理解がしやすいかと思い、コピペだけで簡単に学べるチュートリアルを作成しました。

それぞれの違いについては公式ドキュメントでも、数多くのブログ記事などでも解説されています。

ここに挙げただけでなく、数え切れないくらい多くの記事で解説されています。また、私も以下の記事を書いています。

事前準備

まずは今回のチュートリアルで利用する数多くの Dockerfile が保存されている GitHub レポジトリをクローンします。

git clone git@github.com:richardimaoka/tutorial-docker-cmd-entrypoint.git
cd tutorial-docker-cmd-entrypoint

CMD の動作理解

まずは CMD だけを先に理解するため、いったん ENTRYPOINT のことを忘れます。これは両者がとても似ていて、同時に理解しようとすると混乱するからです。

JSON array 形式の exec form

Dockerfile.cmd1
FROM ubuntu
CMD ["echo", "abc"]

上記の Dockerfile を build して run しましょう。冒頭でクローンした GitHub レポジトリに Dockerfile.cmd1 というファイルが含まれているので、以下のコマンドをターミナルで実行してください。

以下のコマンドを実行してください
docker build -t cmd1 -f Dockerfile.cmd1 .
docker run --rm cmd1
docker runの結果
abc

次はこちらの Dockerfile です。echo コマンドに渡す引数が 1 つ増えました。

Dockerfile.cmd2
FROM ubuntu
CMD ["echo", "abc", "def"]
以下のコマンドを実行してください
docker build -t cmd2 -f Dockerfile.cmd2 .
docker run --rm cmd2

1 つ増えた分の引数が出力されます。

コマンドの実行結果
abc def

上記 2 例の Dockerfile の CMD は exec form と呼ばれる形式で、Dockerfile リファレンスに、

this is the preferred form

とあるように、CMD の記述には基本的にこの exec form を使うべきです。

shell form

しかし、CMD にはもう一つ、shell form と呼ばれる以下の記法があります。

Dockerfile.cmd3
FROM ubuntu
CMD echo "abc"
以下のコマンドを実行してください
docker build -t cmd3 -f Dockerfile.cmd3 .
docker run --rm cmd3
コマンドの実行結果
abc

shell form で 2 つの引数を渡してみましょう。

Dockerfile.cmd4
FROM ubuntu
CMD echo "abc" "def"
以下のコマンドを実行してください
docker build -t cmd4 -f Dockerfile.cmd4 .
docker run --rm cmd4
コマンドの実行結果
abc def

ここまでの動作は exec form と同じですね。

shell form と exec form によるシェル変数展開

shell form では以下のようにシェル変数の展開も出来ます。

Dockerfile.cmd5
FROM ubuntu
CMD echo "$HOME"
以下のコマンドを実行してください
docker build -t cmd5 -f Dockerfile.cmd5 .
docker run --rm cmd5
コマンドの実行結果
/home/your_username

シェル変数の展開を exec form で行うとなると、少しだけ面倒な記述になります。

Dockerfile.cmd6
FROM ubuntu
CMD [ "sh", "-c", "echo $HOME" ]
以下のコマンドを実行してください
docker build -t cmd6 -f Dockerfile.cmd6 .
docker run --rm cmd6
コマンドの実行結果
/home/your_username

なぜ exec form を優先すべきで、shell form はそうではないのか?

ここまで exec form でも shell form でも同じことが出来る例を見てきました。ここからは両者の違いと、exec form を優先すべき理由の一つを説明します。

以下のコマンドを実行してください
docker pull nginx
docker inspect nginx

nginx の Docker イメージは CMD の exec form を使っていることが確認できます。

コマンド実行結果
"Config": {
    "Cmd": [
        "nginx",
        "-g",
        "daemon off;"
    ]
}
以下のコマンドを実行してください
docker run nginx:1.23.1

これで、nginx によるウェブサーバーが立ち上がります。

コマンド実行結果
2022/09/03 07:14:58 [notice] 1#1: using the "epoll" event method
2022/09/03 07:14:58 [notice] 1#1: nginx/1.23.1
2022/09/03 07:14:58 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/09/03 07:14:58 [notice] 1#1: OS: Linux 5.10.102.1-microsoft-standard-WSL2
2022/09/03 07:14:58 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/09/03 07:14:58 [notice] 1#1: start worker processes
2022/09/03 07:14:58 [notice] 1#1: start worker process 31
2022/09/03 07:14:58 [notice] 1#1: start worker process 32
2022/09/03 07:14:58 [notice] 1#1: start worker process 33
2022/09/03 07:14:58 [notice] 1#1: start worker process 34
2022/09/03 07:14:58 [notice] 1#1: start worker process 35
2022/09/03 07:14:58 [notice] 1#1: start worker process 36
2022/09/03 07:14:58 [notice] 1#1: start worker process 37
2022/09/03 07:14:58 [notice] 1#1: start worker process 38

nginx を停止してみましょう。

以下のショートカットを実行してください
Ctrl + c

これで SIGINT が送られ…

ショートカット実行結果
2022/09/03 07:15:40 [notice] 1#1: signal 2 (SIGINT) received, exiting
2022/09/03 07:15:40 [notice] 1#1: signal 14 (SIGALRM) received
...
...
2022/09/03 07:15:40 [notice] 1#1: worker process 38 exited with code 0
2022/09/03 07:15:40 [notice] 1#1: exit

…nginx が停止しました。

Ctrl+c できれいに停止できるのは、nginx の docker イメージが CMD の exec form を使っているからです。

それでは、shell form を使って nginx を立ち上げるとどうなるか見てみましょう。shell form を使った nginx のコンテナを作成します。

Dockerfile.cmd-nginx
FROM nginx:1.23.1
CMD nginx -g "daemon off;"
以下のコマンドを実行してください
docker build -t cmd-nginx -f Dockerfile.cmd-nginx .
docker run --rm cmd-nginx
以下のショートカットを実行してください
Ctrl + c
実行結果
2022/09/03 05:48:46 [notice] 7#7: start worker process 13
2022/09/03 05:48:46 [notice] 7#7: start worker process 14
2022/09/03 05:48:46 [notice] 7#7: start worker process 15
^C

Ctrl+c を押しても nginx が停止しません!

代わりに、別ターミナルで docker stop を実行すれば、これを停止できます。

【別ターミナル】で以下のコマンドを実行してください
docker stop cnt-cmd-nginx

先ほどの exec form の例と違ってsignal 2 (SIGINT) received, exitingとは表示されずに、急に nginx のログが出力されなくなり停止しました。

docker stop で停止できる理由

docker stop のリファレンスにあるとおり、最初に SIGTERM が送られるのですが…

The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL.

…CMD に shell form を使っていることにより SIGTERM は無視され(理由は後述)、2 番めに送られる SIGKILL によって nginx が強制終了されるためです。

Ctrl+c で送ったのは SIGINT であり SIGTERM ではないので、比較になっていないのでは?

SIGINT でも SIGTERM でほぼ同じ結果になります。

以下のコマンドを実行してください
docker run nginx

別ターミナルdocker killを使って SIGTERM を送ります。

【別ターミナル】で以下のコマンドを実行してください
docker kill --signal SIGTERM cnt-cmd-nginx

SIGTERM でも SIGINT と同様 nginx は停止します。

コマンド実行結果
2022/09/03 06:04:49 [notice] 1#1: signal 15 (SIGTERM) received, exiting
2022/09/03 06:04:49 [notice] 31#31: exiting
...
...
2022/09/03 06:04:49 [notice] 1#1: worker process 38 exited with code 0
2022/09/03 06:04:49 [notice] 1#1: exit

それでは、shell form を使った場合に Ctrl+c で送信される SIGINT を送信するとどうなるでしょう?

以下のコマンドを実行してください
docker build -t cmd-nginx -f Dockerfile.cmd-nginx .
docker run --name cnt-cmd-nginx cmd-nginx

別ターミナルdocker killを使って SIGINT を送ります。

【別ターミナル】で以下のコマンドを実行してください
docker kill --signal SIGINT cnt-cmd-nginx

shell form では、SIGTERM 同様 SIGINT も無視され、nginx は走り続けます。

2022/09/03 05:48:46 [notice] 7#7: start worker process 14
2022/09/03 05:48:46 [notice] 7#7: start worker process 15

以上のように、プロセス・シグナルを受け取れなくなってしまうことが、shell form の欠点であり、exec form を使うべき大きな理由です。

exec form と shell form の PID 1 を比較する

shell form でプロセス・シグナルを受け取れなくなってしまうのは、CMD で指定したコマンドがコンテナ内のプロセス IDPID 1にならないからです。

exec form で、CMD のコマンドとコンテナ内のPID 1の一致を確かめましょう。

Dockerfile.cmd7
FROM ubuntu
CMD ["tail", "-f", "/dev/null"]
以下のコマンドを実行してください
docker build -t cmd7 -f Dockerfile.cmd7 .
docker run --name cnt-cmd7 --rm cmd7

何も表示されずプロセスが立ち上がったままになるので、別ターミナルからコンテナ内部に入ります。

【別ターミナル】で以下のコマンドを実行してください
docker exec -it cnt-cmd7 /bin/sh

これでコンテナ内部に入り込めたので、ps コマンドでコンテナ内部のプロセス一覧を表示します。

以下のコマンドを実行してください
ps -eaf

CMD で指定したコマンドtailPID 1になっていることがわかります。

コマンド実行結果
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 06:49 ?        00:00:00 tail -f /dev/null
root         7     0  1 06:50 pts/0    00:00:00 /bin/sh
root        13     7  0 06:50 pts/0    00:00:00 ps -eaf

コンテナ内から抜けましょう。

以下のコマンドを実行してください
exit

tail コマンドは nginx とは違って SIGTERMを受け付けて終了してくれないので、SIGKILLで強制終了します。

以下のコマンドを実行してください
docker kill --signal SIGKILL cnt-cmd7

次に、shell form の CMD です。PID 1tailにならないことを確かめましょう。

Dockerfile.cmd8
FROM ubuntu
CMD tail -f /dev/null
以下のコマンドを実行してください
docker build -t cmd8 -f Dockerfile.cmd8 .
docker run --name cnt-cmd8 --rm cmd8

何も表示されずに、プロセスが立ち上がったままになるので、別ターミナルで以下を実行してください。

【別ターミナル】で以下のコマンドを実行してください
docker exec -it cnt-cmd8 /bin/sh

これでコンテナ内部に入り込めたので、ps コマンドでコンテナ内部のプロセス一覧を表示します。

以下のコマンドを実行してください
ps -eaf

注意してみないと分かりづらいですが、PID 1/bin/shになっています。tailPID 7です。

コマンド実行結果
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 06:37 ?        00:00:00 /bin/sh -c tail -f /dev/null
root         7     1  0 06:37 ?        00:00:00 tail -f /dev/null
root         8     0  0 06:40 pts/0    00:00:00 /bin/sh
root        15     8  0 06:41 pts/0    00:00:00 ps -eaf
以下のコマンドを実行してください
exit

SIGKILLで強制終了します。

以下のコマンドを実行してください
docker kill --signal SIGKILL cnt-cmd8

ENTRYPOINT の動作理解

CMD の次は ENTRYPOINT の動作理解です。ここでも ENTRYPOINT の動作だけに集中して、CMD のことは忘れます。

JSON array 形式の exec form

Dockerfile.entrypoint1
FROM ubuntu
ENTRYPOINT ["echo", "abc"]

上記の Dockerfile を build して run しましょう

以下のコマンドを実行してください
docker build -t entrypoint1 -f Dockerfile.entrypoint1 .
docker run --rm entrypoint1
コマンドの実行結果
abc

次はこちらの Dockerfile です。echo コマンドに渡す引数が 1 つ増えました。

Dockerfile.entrypoint2
FROM ubuntu
ENTRYPOINT ["echo", "abc", "def"]
以下のコマンドを実行してください
docker build -t entrypoint2 -f Dockerfile.entrypoint2 .
docker run --rm entrypoint2
コマンドの実行結果
abc def

上記 2 例の Dockerfile の ENTRYPOINT は exec form と呼ばれる形式で、Dockerfile リファレンスに、

The exec form, which is the preferred form

とあるように、ENTRYPOINT の記述も基本的にこの exec form を使うべきです。

shell form

ENTRYPOINT にはもう一つ、shell form と呼ばれる以下の記法があります。

Dockerfile.entrypoint3
FROM ubuntu
ENTRYPOINT echo "abc"
以下のコマンドを実行してください
docker build -t entrypoint3 -f Dockerfile.entrypoint3 .
docker run --rm entrypoint3
コマンドの実行結果
abc

shell form で 2 つの引数を渡してみましょう。

Dockerfile.entrypoint4
FROM ubuntu
ENTRYPOINT echo "abc" "def"
以下のコマンドを実行してください
docker build -t entrypoint4 -f Dockerfile.entrypoint4 .
docker run --rm entrypoint4
コマンドの実行結果
abc def

ここまでの動作は exec form と同じですね。

shell form と exec form によるシェル変数展開

shell form では以下のようにシェル変数の展開も出来ます。

Dockerfile.entrypoint5
FROM ubuntu
ENTRYPOINT echo "$HOME"
以下のコマンドを実行してください
docker build -t entrypoint5 -f Dockerfile.entrypoint5 .
docker run --rm entrypoint5
コマンドの実行結果
/home/your_username

シェル変数の展開を exec form で行うとなると、少しだけ面倒な記述になります。

Dockerfile.entrypoint6
FROM ubuntu
ENTRYPOINT [ "sh", "-c", "echo $HOME" ]
以下のコマンドを実行してください
docker build -t entrypoint6 -f Dockerfile.entrypoint6 .
docker run --rm entrypoint6
コマンドの実行結果
/home/your_username

なぜ exec form を優先すべきで、shell form はそうではないのか?

ここまで exec form でも shell form でも同じことが出来る例を見てきました。ここからは両者の違いと、exec form を優先すべき理由の一つを説明します。

Dockerfile.entrypoint-nginx1
FROM nginx:1.23.1
ENTRYPOINT [ "nginx", "-g", "daemon off;"]
以下のコマンドを実行してください
docker build -t entrypoint-nginx1 -f Dockerfile.entrypoint-nginx1 .
docker inspect entrypoint-nginx1

CMD は null になり、ENTRYPOINT のみが残りました。

"Config": {
    ...
    "Cmd": null,
    "Entrypoint": [
        "nginx",
        "-g",
        "daemon off;"
    ]
}

これは、ベースイメージの nginx には CMD が定義されていましたが、上記のDockerfile.entrypoint-nginx1で ENTRYPOINT を指定したためです。Dockerfile のリファレンスにも下記の記述があります。

If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value.

それではコンテナを立ち上げましょう。

以下のコマンドを実行してください
docker run --rm entrypoint-nginx1
コマンド実行結果
2022/09/03 07:14:58 [notice] 1#1: using the "epoll" event method
2022/09/03 07:14:58 [notice] 1#1: nginx/1.23.1
2022/09/03 07:14:58 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/09/03 07:14:58 [notice] 1#1: OS: Linux 5.10.102.1-microsoft-standard-WSL2
2022/09/03 07:14:58 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/09/03 07:14:58 [notice] 1#1: start worker processes
2022/09/03 07:14:58 [notice] 1#1: start worker process 31
2022/09/03 07:14:58 [notice] 1#1: start worker process 32
2022/09/03 07:14:58 [notice] 1#1: start worker process 33
2022/09/03 07:14:58 [notice] 1#1: start worker process 34
2022/09/03 07:14:58 [notice] 1#1: start worker process 35
2022/09/03 07:14:58 [notice] 1#1: start worker process 36
2022/09/03 07:14:58 [notice] 1#1: start worker process 37
2022/09/03 07:14:58 [notice] 1#1: start worker process 38

nginx を停止してみましょう。

以下のショートカットを実行してください
Ctrl + c

これで SIGINT が送られ…

ショートカット実行結果
2022/09/03 07:15:40 [notice] 1#1: signal 2 (SIGINT) received, exiting
2022/09/03 07:15:40 [notice] 1#1: signal 14 (SIGALRM) received
...
...
2022/09/03 07:15:40 [notice] 1#1: worker process 38 exited with code 0
2022/09/03 07:15:40 [notice] 1#1: exit

…nginx が停止しました。

Ctrl+c できれいに停止できるのは、先程ファイルDockerfile.entrypoint-nginx1で見たように、ENTRYPOINT の exec form を使っているからです。

それでは、shell form を使って nginx を立ち上げるとどうなるか見てみましょう。

Dockerfile.entrypoint-nginx2
FROM nginx:1.23.1
ENTRYPOINT nginx -g "daemon off;"
以下のコマンドを実行してください
docker build -t entrypoint-nginx2 -f Dockerfile.entrypoint-nginx2 .
docker run --name cnt-entrypoint-nginx2 --rm entrypoint-nginx2
以下のショートカットを実行してください
Ctrl + c
2022/09/03 05:48:46 [notice] 7#7: start worker process 13
2022/09/03 05:48:46 [notice] 7#7: start worker process 14
2022/09/03 05:48:46 [notice] 7#7: start worker process 15
^C

Ctrl+c を押しても nginx が停止しません!

代わりに、別ターミナルで docker stop を実行すればこれを停止できます。

【別ターミナル】で以下のコマンドを実行してください
docker stop cnt-entrypoint-nginx2

先ほどと違ってsignal 2 (SIGINT) received, exitingとは表示されずに、急に nginx のログが出力されなくなり停止しました。

docker stop で停止できる理由

docker stop のリファレンスにあるとおり、最初に SIGTERM が送られるのですが…

The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL. The first signal can be changed with the STOPSIGNAL instruction in the container’s Dockerfile, or the --stop-signal option to docker run.

…ENTRYPOINT に shell form を使っていることにより SIGTERM は無視され(理由は後述)、2 番めに送られる SIGKILL によって nginx が強制終了されるためです。

Ctrl+c で送ったのは SIGINT であり SIGTERM ではないので、比較になっていないのでは?

SIGINT でも SIGTERM でほぼ同じ結果になります。Ctrl+c = SIGINT で停止した exec form のコンテナを、SIGTERM で停止させてみましょう。

以下のコマンドを実行してください
docker run --name cnt-entrypoint-nginx1 --rm entrypoint-nginx1

別ターミナルdocker killを使って SIGTERM を送ります。

【別ターミナル】で以下のコマンドを実行してください
docker kill --signal SIGTERM cnt-entrypoint-nginx1

SIGTERM でも SIGINT と同様 nginx は停止します。

コマンド実行結果
2022/09/03 06:04:49 [notice] 1#1: signal 15 (SIGTERM) received, exiting
2022/09/03 06:04:49 [notice] 31#31: exiting
...
...
2022/09/03 06:04:49 [notice] 1#1: worker process 38 exited with code 0
2022/09/03 06:04:49 [notice] 1#1: exit

それでは、shell form を使った場合に Ctrl+c で送信される SIGINT を送信するとどうなるでしょう?

以下のコマンドを実行してください
docker run --name cnt-entrypoint-nginx2 --rm entrypoint-nginx2

別ターミナルdocker killを使って SIGINT を送ります。

【別ターミナル】で以下のコマンドを実行してください
docker kill --signal SIGINT cnt-entrypoint-nginx2

shell form では、SIGTERM 同様 SIGINT も無視され、nginx は走り続けます。

2022/09/03 05:48:46 [notice] 7#7: start worker process 14
2022/09/03 05:48:46 [notice] 7#7: start worker process 15

以上のように、プロセス・シグナルを受け取れなくなってしまうことが、shell form の欠点であり、exec form を使うべき大きな理由です。

exec form と shell form の PID 1 を比較する

shell form でプロセス・シグナルを受け取れなくなってしまうのは、ENTRYPOINT で指定したコマンドがコンテナ内のプロセス IDPID 1にならないからです。

exec form ENTRYPOINT のコマンドとコンテナ内のPID 1の一致を確かめましょう。

Dockerfile.entrypoint7
FROM ubuntu
ENTRYPOINT ["tail", "-f", "/dev/null"]
以下のコマンドを実行してください
docker build -t entrypoint7 -f Dockerfile.entrypoint7 .
docker run --name cnt-entrypoint7 --rm entrypoint7

何も表示されずプロセスが立ち上がったままになるので、別ターミナルからコンテナ内部に入ります。

【別ターミナル】で以下のコマンドを実行してください
docker exec -it cnt-entrypoint7 /bin/sh

これでコンテナ内部に入り込めたので、ps コマンドでコンテナ内部のプロセス一覧を表示します。

以下のコマンドを実行してください
ps -eaf

ENTRYPOINT で指定したコマンドtailPID 1になっていることがわかります。

コマンド実行結果
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 06:49 ?        00:00:00 tail -f /dev/null
root         7     0  1 06:50 pts/0    00:00:00 /bin/sh
root        13     7  0 06:50 pts/0    00:00:00 ps -eaf

コンテナ内から抜けましょう。

以下のコマンドを実行してください
exit

tail コマンドは nginx とは違って SIGTERMを受け付けて終了してくれないので、SIGKILLで強制終了します。

以下のコマンドを実行してください
docker kill --signal SIGKILL cnt-entrypoint7

次に、shell form の ENTRYPOINT です。PID 1tailにならないことを確かめましょう。

Dockerfile.entrypoint8
FROM ubuntu
ENTRYPOINT tail -f /dev/null
以下のコマンドを実行してください
docker build -t entrypoint8 -f Dockerfile.entrypoint8 .
docker run --name cnt-entrypoint8 --rm entrypoint8

何も表示されずに、プロセスが立ち上がったままになるので、別ターミナルで以下を実行してください

【別ターミナル】で以下のコマンドを実行してください
docker exec -it cnt-entrypoint8 /bin/sh

これでコンテナ内部に入り込めたので、ps コマンドでコンテナ内部のプロセス一覧を表示します。

以下のコマンドを実行してください
ps -eaf

注意してみないと分かりづらいですが、PID 1/bin/shになっています。tailPID 7です。

コマンド実行結果
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 06:37 ?        00:00:00 /bin/sh -c tail -f /dev/null
root         7     1  0 06:37 ?        00:00:00 tail -f /dev/null
root         8     0  0 06:40 pts/0    00:00:00 /bin/sh
root        15     8  0 06:41 pts/0    00:00:00 ps -eaf
以下のコマンドを実行してください
exit

SIGKILLで強制終了します。

以下のコマンドを実行してください
docker kill --signal SIGKILL cnt-entrypoint8

CMD と ENTRYPOINT 両方を指定する場合の動作理解

ここまでの例では CMD も ENTRYPOINT もほぼ同じ動作をしました。これでは、なぜ似たようなものが 2 つ存在するか理由がわからないと思うので、2 つを組み合わせた使い方を学んでみましょう。

Dockerfile.cmd-and-entrypoint1
FROM ubuntu
ENTRYPOINT [ "echo" ]
CMD ["abc"]
以下のコマンドを実行してください
docker build -t cmd-and-entrypoint1 -f Dockerfile.cmd-and-entrypoint1 .
docker run --rm cmd-and-entrypoint1

ENTRYPOINT と CMD を組み合わせたecho abcが実行されます。

コマンドの実行結果
abc

ENTRYPOINT の echo がコマンド、CMD の"abd"がデフォルト引数になっています。Dockerfile のリファレンスにあるとおりです。

If CMD is used to provide default arguments for the ENTRYPOINT instruction, both the CMD and ENTRYPOINT instructions should be specified with the JSON array format.

それでは、docker run 時に引数を与えてみましょう。

以下のコマンドを実行してください
docker run --rm cmd-and-entrypoint1 def

出力がabc defではなくdefのみであることに注意してください。

コマンドの実行結果
def

デフォルト引数である CMD の"abc"は無視され、docker run で与えた"def"のみが出力されました。

さらに追加の引数を与えると…

以下のコマンドを実行してください
docker run --rm cmd-and-entrypoint1 def ghi

…こうなります

コマンドの実行結果
def ghi

以上の動作を CMD だけ指定したときの動作と比べてみましょう。冒頭で使ったDockerfile.cmd1を再掲します。

Dockerfile.cmd1
FROM ubuntu
CMD ["echo", "abc"]

これを、docker run で引数defを与えて実行しましょう。

以下のコマンドを実行してください
docker build -t cmd1 -f Dockerfile.cmd1 .
docker run --rm cmd1 def

defが引数ではなく、コマンドとして解釈されてしまったためエラーになります。

コマンドの実行結果
docker: Error response from daemon: failed to create shim task:
 OCI runtime create failed: runc create failed:
   unable to start container process: exec: "def":
     executable file not found in $PATH: unknown.

それでは ENTRYPOINT だけの場合はどうでしょう?

Dockerfile.entrypoint1
FROM ubuntu
ENTRYPOINT ["echo", "abc"]
以下のコマンドを実行してください
docker build -t entrypoint1 -f Dockerfile.entrypoint1 .
docker run --rm entrypoint1 def

ENTRYPOINT のコマンドである echo abcに更に引数defを追加した結果になりました。

コマンドの実行結果
abc def

これは Dockerfile のリファレンスにあるとおりです。

Command line arguments to docker run <image> will be appended after all elements in an exec form ENTRYPOINT, and will override all elements specified using CMD.

docker run 時に渡す引数について、CMD と ENTRYPOINT が異なる動作をすることがわかりました。

CMD を ENTRYPOINT のデフォルト引数とする使い方

ENTRYPOINT と CMD を組み合わせれば、デフォルト引数で最もよく使われるケースに対応しつつ、必要に応じて引数を上書きできる柔軟性を持ったコンテナを作成できます。

pingを実行するコンテナを作成し、デフォルトでlocalhostを ping するとしましょう。

Dockerfile.cmd-and-entrypoint2
FROM centos
ENTRYPOINT [ "ping" ]
CMD ["localhost"]
以下のコマンドを実行してください
docker build -t cmd-and-entrypoint2 -f Dockerfile.cmd-and-entrypoint2 .
docker run --rm cmd-and-entrypoint2
実行結果
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=5.09 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.027 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.023 ms
64 bytes from localhost (127.0.0.1): icmp_seq=4 ttl=64 time=0.021 ms

ping 出来ることを確認したので、コンテナを止めましょう。

以下のショートカットを実行してください
Ctrl + c

これがもし以下のような Dockerfile であれば、localhost しか ping 出来ないコンテナになってしまいます。

Dockerfile.cmd-and-entrypoint3
FROM ubuntu
ENTRYPOINT [ "ping", "localhost"]

実際には ping のためだけのコンテナを作ることはないでしょうが、ENTRYPOINT に docker run で上書きしたい引数部分を含まないようにする、上書きしたい部分はデフォルト引数として CMD で与えるというのは重要になります。

さらに学ぶには - 実際の Docker オフィシャル・イメージ

以下の記事でいくつかのオフィシャル・イメージを例に挙げながら、CMD と ENTRYPOINT の使い分けを説明しています。

GitHubで編集を提案

Discussion