コピペで学ぶチュートリアル: DockerfileのCMDとENTRYPOINTの違い
Dockerfile の CMD と ENTRYPOINT について、手を動かして動作確認できるチュートリアルがあると理解がしやすいかと思い、コピペだけで簡単に学べるチュートリアルを作成しました。
それぞれの違いについては公式ドキュメントでも、数多くのブログ記事などでも解説されています。
- Dockerfile reference
- Best practices for writing Dockerfiles
- ENTRYPOINT は「必ず実行」、CMD は「(デフォルトの)引数」 - POCKETSTUDIO.NET (Docker 界隈で有名な前佛 雅人さんのブログ)
ここに挙げただけでなく、数え切れないくらい多くの記事で解説されています。また、私も以下の記事を書いています。
事前準備
まずは今回のチュートリアルで利用する数多くの 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
FROM ubuntu
CMD ["echo", "abc"]
上記の Dockerfile を build して run しましょう。冒頭でクローンした GitHub レポジトリに Dockerfile.cmd1 というファイルが含まれているので、以下のコマンドをターミナルで実行してください。
docker build -t cmd1 -f Dockerfile.cmd1 .
docker run --rm cmd1
abc
次はこちらの Dockerfile です。echo コマンドに渡す引数が 1 つ増えました。
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 と呼ばれる以下の記法があります。
FROM ubuntu
CMD echo "abc"
docker build -t cmd3 -f Dockerfile.cmd3 .
docker run --rm cmd3
abc
shell form で 2 つの引数を渡してみましょう。
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 では以下のようにシェル変数の展開も出来ます。
FROM ubuntu
CMD echo "$HOME"
docker build -t cmd5 -f Dockerfile.cmd5 .
docker run --rm cmd5
/home/your_username
シェル変数の展開を exec form で行うとなると、少しだけ面倒な記述になります。
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 のコンテナを作成します。
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
の一致を確かめましょう。
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 で指定したコマンドtail
がPID 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 1
がtail
にならないことを確かめましょう。
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
になっています。tail
はPID 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
FROM ubuntu
ENTRYPOINT ["echo", "abc"]
上記の Dockerfile を build して run しましょう
docker build -t entrypoint1 -f Dockerfile.entrypoint1 .
docker run --rm entrypoint1
abc
次はこちらの Dockerfile です。echo コマンドに渡す引数が 1 つ増えました。
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 と呼ばれる以下の記法があります。
FROM ubuntu
ENTRYPOINT echo "abc"
docker build -t entrypoint3 -f Dockerfile.entrypoint3 .
docker run --rm entrypoint3
abc
shell form で 2 つの引数を渡してみましょう。
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 では以下のようにシェル変数の展開も出来ます。
FROM ubuntu
ENTRYPOINT echo "$HOME"
docker build -t entrypoint5 -f Dockerfile.entrypoint5 .
docker run --rm entrypoint5
/home/your_username
シェル変数の展開を exec form で行うとなると、少しだけ面倒な記述になります。
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 を優先すべき理由の一つを説明します。
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 を立ち上げるとどうなるか見てみましょう。
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
の一致を確かめましょう。
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 で指定したコマンドtail
がPID 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 1
がtail
にならないことを確かめましょう。
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
になっています。tail
はPID 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 つを組み合わせた使い方を学んでみましょう。
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
を再掲します。
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 だけの場合はどうでしょう?
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 するとしましょう。
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 出来ないコンテナになってしまいます。
FROM ubuntu
ENTRYPOINT [ "ping", "localhost"]
実際には ping のためだけのコンテナを作ることはないでしょうが、ENTRYPOINT に docker run で上書きしたい引数部分を含まないようにする、上書きしたい部分はデフォルト引数として CMD で与えるというのは重要になります。
さらに学ぶには - 実際の Docker オフィシャル・イメージ
以下の記事でいくつかのオフィシャル・イメージを例に挙げながら、CMD と ENTRYPOINT の使い分けを説明しています。
Discussion