CMD
は一見「デフォルトの実行コマンド」を書くだけの単純な命令ですが、ENTRYPOINT
との組み合わせやシェル/exec 形式、ラッパースクリプトの書き方次第でコンテナの挙動は大きく変わります。本記事は中〜上級者向けに、CMD
の仕様・実践パターン・落とし穴・デバッグ手順・運用に耐える設計までを実例付きで解説します。
1. CMD
の基本と実行形式(おさらい)
フォーム
- exec 形式(推奨)
CMD ["executable", "arg1", "arg2"]
- シェル形式(非推奨が多い)
CMD executable arg1 arg2
なぜ exec 形式を推奨するか
- exec 形式はプロセスを直接起動するため、シグナル(SIGTERM / SIGINT 等)がアプリケーションにそのまま届きやすい。
- シェル形式は
/bin/sh -c
を経由するため、シグナル伝搬やプロセスツリーが複雑化しやすい(PID 1 問題を招く)。
2. ENTRYPOINT
と CMD
の責務分離
- ENTRYPOINT:コンテナ「必ず実行する実体(プログラム)」を定義。コンテナの役割を固定するのに使う。
- CMD:ENTRYPOINT に渡すデフォルト引数、または単独でイメージのデフォルト実行コマンドを指定する。
典型パターン:
ENTRYPOINT ["python3", "app.py"]
CMD ["--port", "8080"]
上記は docker run image
で python3 app.py --port 8080
が実行され、docker run image --port 9000
とすると引数が上書きされます。
3. よくある誤解と落とし穴
3.1 ENTRYPOINT がシェルスクリプトの場合の落とし穴
シェルスクリプトでラップする際、最後に exec "$@"
を書かないと、実行されたプロセスはシェルの子プロセスになり、シグナルが伝搬されません(PID 1 がシェルのまま)。例:
entry.sh
(NG例)
#!/bin/sh
echo "starting..."
"$@"
修正版(OK)
#!/bin/sh
echo "starting..."
exec "$@"
exec
を使うことでシェルを置換し、実アプリが PID 1 になります。
3.2 シェル形式による信号伝搬問題
CMD echo hello
のようなシェル形式は /bin/sh -c
を経由するため、docker stop
時に SIGTERM がコンテナに届かないことがあります。常に exec 形式か exec
を使ったラッパーにしましょう。
4. デバッグ手順(実践的)
- イメージの設定確認
docker inspect --format '{{.Config.Entrypoint}}' image docker inspect --format '{{.Config.Cmd}}' image
- コンテナを対話で起動して確認
docker run --rm -it --entrypoint /bin/sh image
- 実行プロセスの確認
docker run --rm -d --name test image docker exec -it test ps aux docker logs test
docker run
で CMD を上書いて挙動確認docker run --rm image custom-arg
5. ハンズオン:CMD
挙動を体感する(3 ステップ)
Step A — CMD のみ
Dockerfile
FROM alpine:3.20
CMD ["echo", "Hello from CMD"]
実行:
docker build -t cmd-only .
docker run --rm cmd-only
# => Hello from CMD
Step B — ENTRYPOINT + CMD
Dockerfile
FROM alpine:3.20
ENTRYPOINT ["echo"]
CMD ["World"]
実行:
docker build -t entry-cmd .
docker run --rm entry-cmd # => World
docker run --rm entry-cmd "Custom" # => Custom
Step C — ラッパースクリプトでの修正例
entry.sh
#!/bin/sh
set -e
# 前処理
: "prepare"
exec "$@"
Dockerfile
FROM alpine
COPY entry.sh /entry.sh
RUN chmod +x /entry.sh
ENTRYPOINT ["/entry.sh"]
CMD ["sleep", "3600"]
docker run
で exec
によるプロセス置換を確認する。
6. 上級テクニック
6.1 環境変数を使った動的 CMD
Dockerfile 内で環境変数展開は限定的(ENV
を利用)。もしビルド時に決められない値を CMD に渡したいなら、ENTRYPOINT ラッパーで環境変数を解決して exec
するパターンが有効。
例(entrypoint が env をコマンドに組み込む):
#!/bin/sh
: "${PORT:=8080}"
exec myserver --port "$PORT"
6.2 tini
/ dumb-init
の利用
PID 1 問題をライブラリで解決する。軽量 init を ENTRYPOINT にして、以後のプロセス管理を委任する。
FROM python:3.12-slim
RUN apt-get update && apt-get install -y tini
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["python", "app.py"]
6.3 docker-compose と CMD の上書き
docker-compose.yml
の command:
はイメージの CMD を上書きする。複数の環境(dev/prod)でデフォルトを変えたいときは compose の override
を活用。
services:
web:
image: myimage
command: ["--debug"]
6.4 CI/CD とイメージ設計
- テスト用イメージは
CMD
を軽くしてdocker run
で簡単に検証可能にしておく。 - 本番用は
ENTRYPOINT
に堅牢なラッパーを置き、ログ/メトリクスの初期化を行う。
7. セキュリティ・運用面での注意点
CMD
/ENTRYPOINT
で root 権限のコマンドを直接実行しない(USER
命令で非特権ユーザ切替を活用)。- 実行ファイルやラッパーが外部からの環境変数を安易に使用するとコマンドインジェクションのリスクがあるため、入力検証を行う。
- ロギングは stdout/stderr に集める(コンテナ原則に従う)。
8. まとめとチェックリスト
主要ポイント
CMD
は「デフォルト」:上書きされることを前提に設計する。ENTRYPOINT
は「役割固定」:CMD はその引数として使うのが責務分離の王道。- exec 形式を使い、ラッパースクリプトなら
exec "$@"
を忘れない。 - PID 1 問題には
tini
やexec
を活用する。 docker inspect
/docker run --entrypoint
/ps aux
を使って挙動を必ず検証する。
コメントを残す