Agent Grow Tech Notes
🐳

DockerfileのCMDの記述の仕方

2024/11/05に公開

DockerfileにおけるCMDの書き方の理解が浅いと感じたため、CMD命令について深掘りしていきたいと思います。

CMDとは

Dockerfileにはコンテナイメージをビルドし、コンテナ起動時にコマンドを実行できるように指定する命令があります。それがCMDです。
似たような命令にRUNがありますが、コンテナイメージをビルド時に実行される命令です。

CMDとRUNの挙動の違い

もう少し具体的なコマンドを例に説明すると
CMDの場合run要するにコンテナを起動コマンド実行時にDockerfileに記述されているCMD命令が実行されます。

docker container run -d --name <container-name>:<tag> 

RUNの場合build要するにコンテナイメージをビルドする際にDockerfileに記述されているRUN命令が実行されます。

docker image build -t amazonlinux:2023.5.20241001.1 .

CMDの書き方

CMDに話を戻しましょう。
DockerfileのCMDの書き方にはshell形式とexec形式の二つの書き方があります。
結論から申し上げますと、二つの形式の違いは以下です。

  • shell形式は実行したいコマンドの前に/bin/sh -c[1]が付与され、シェルを経由してコマンドを解釈し実行する
  • exec形式はシェルを経由せずともDockerfile内のコマンドを直接実行する

shell形式

# ベースイメージの指定
FROM amazonlinux:2023.5.20241001.1

# 任意の環境変数の設定
ENV MY_VAR="Hello from CMD"

# CMD命令の指定
CMD echo "This message is from CMD: $MY_VAR"

コマンドを文字列として記述し、/bin/sh -cをラップされ、/bin/shが親プロセスとなり、指定したコマンドが子プロセスとして実行されます。
もう少し具体的に説明します。

/bin/sh -cをラップされ

/bin/sh -cでラップされるというのは、実行したいコマンドの前に/bin/sh -cが追加されシェルを通してコマンドが実行されるという意味です。
例えると
シェルはLinuxでコマンドを解釈して実行する通訳する人です。

つまり、Dockerが自動的にコマンドの前に/bin/sh -cのシェルを使って指定したコマンドを実行してくださいね、という指示をしているのがshell形式のCMD命令となります。

CMDの命令句では以下のように記述していますが

CMD echo "This message is from CMD: $MY_VAR"

実際には/bin/shが起動され、echo以降のコマンドの内容を解釈して実行しています。

/bin/sh -c "echo 'This message is from CMD: $MY_VAR'"

実際にコンテナを作ってechoの出力がされているか確認してみましょう。
コンテナイメージをビルドします。

docker image build -t amazonlinux:2023.5.20241001.1 .

イメージが作成されたことを確認します。

docker image ls
REPOSITORY                                                     TAG                                                                          IMAGE ID       CREATED         SIZE
amazonlinux                                                    2023.5.20241001.1                                                            664459dbc002   4 weeks ago     144MB

コンテナを起動します。

docker container run -d --name amazonlinux amazonlinux:2023.5.20241001.1

echoの出力結果を確認します。

docker logs amazonlinux
This message is from CMD: Hello from CMD

環境変数も含めて問題なく実行されていることが確認できますね。
exec形式でも同じような挙動をこれから確認していきますが、ここで少しだけexec形式で検証する前に念頭に置いておいてほしいことがあります。
shell形式の場合、自動的にコマンド実行の前に/bin/sh -cのシェルを使ってコマンドを実行するように指示をし、環境変数も展開してコマンドを実行しますが、CMDの場合明示的に/bin/sh -cを指定しないと環境変数を展開してくれません。
実際に検証してみましょう。

exec形式

まずは以下のDockerfileを使ってコンテナイメージをbuildrunを実行します。(コマンドの内容は同様のため省略します)

# ベースイメージの指定
FROM amazonlinux:2023.5.20241001.1

# 環境変数の設定(必要であれば)
ENV MY_VAR="Hello from CMD"

# CMD命令の指定 (exec形式)
CMD ["echo", "This message is from CMD:", "$MY_VAR"]

docker container runを実行後docker logsを実行した結果は以下となりました。

docker logs amazonlinux
This message is from CMD: $MY_VAR

環境変数が展開されず環境変数の文字列がそのまま実行されていますね。
ここで前述していた以下の通りになったことがわかります。

CMDの場合明示的に/bin/sh -cを指定しないと環境変数を展開してくれません。

環境変数を展開したい場合にはDockerfileの内容を以下のように修正する必要があります。
修正した箇所は["sh","-c"]と明示的に指定し、/bin/sh -cのシェルを使って指定したコマンドを実行してくださいね、と指示をします。

# ベースイメージの指定
FROM amazonlinux:2023.5.20241001.1

# 環境変数の設定(必要であれば)
ENV MY_VAR="Hello from CMD"

# CMD命令の指定 (exec形式)
CMD ["sh","-c","echo This message is from CMD: $MY_VAR"]

ではこのDockerfileを使用してdocker logsの結果を見てみましょう。(build,runのコマンドの内容は同様のため省略します)

docker logs amazonlinux
This message is from CMD: Hello from CMD

今回はちゃんと環境変数が展開されていることが確認できますね!

まとめ

CMDの書き方と実際の挙動の違いについてまとめてみました。
このCMDの書き方次第ではコンテナが全然起動してこない、やログに出力されるはずの内容が出力されない、といったことでハマることがありますので(実際にハマりました)詳細を理解しておく必要があると感じ今回アウトプットしました。
この記事がどなたかの参考になれば幸いです。

脚注
  1. /bin/sh -c-cオプションの意味は-cオプションの後に続く文字列を1つのコマンドとして認識します。 ↩︎

Agent Grow Tech Notes
Agent Grow Tech Notes

Discussion