Docker Composeのcommandとentrypointの違いと使い分け
Docker ComposeではENTRYPOINTが実行するプログラム、commandが引数です。4つの上書きパターンと、entrypoint設定でイメージのCMDが破棄される仕様を確認します。
Docker Compose の command と entrypoint を 1 つのルールで理解する
Docker Compose では、entrypoint: が実行するプログラムを指定し、command: がそのプログラムに渡す引数を指定します。コンテナのプロセスは、entrypoint のリストの末尾に command のリストを追加したものです。このページの他の動作は、すべてこの 1 文から導けます。
この 2 つのキーは、Dockerfile の 2 つの命令に対応します。entrypoint: はイメージの ENTRYPOINT を置き換えます。command: はイメージの CMD を置き換えます。これらは独立していません。ここで混乱が生じます。entrypoint: を設定すると、イメージの CMD も破棄されます。Compose 仕様には、この動作が明記されています。entrypoint が null でない場合、Compose はイメージのデフォルトコマンドを無視します。
イメージがすでに宣言している内容を確認する
何かを上書きする前に、イメージに含まれる設定を確認します。
docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16["docker-entrypoint.sh"] と ["postgres"] が表示されるため、コンテナは docker-entrypoint.sh postgres を実行します。このスクリプトは初回起動時にデータディレクトリを作成し、POSTGRES_* 変数を読み込み、権限を postgres ユーザーに下げ、最後に渡された引数を exec で実行します。どの部分を変更するかを把握することが、判断の要点です。データベースにフラグを渡すには、command: を置き換えます。entrypoint: を置き換えると、この初期設定は一切実行されません。
4 つの組み合わせを小さなイメージで確認する
起動時に渡された引数の一覧を表示することだけを目的とするイメージを作成します。
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemo編集するたびに docker compose up を実行し、ログに出力された 1 行を確認します。
- どちらのキーも未設定。 プロセスは
/bin/echo ep cmdで、ログにはep cmdが表示されます。 command: ["cmd2"]のみ。 プロセスは/bin/echo ep cmd2です。entrypoint は変更されず、引数だけが変わります。entrypoint: ["/bin/echo", "ep2"]のみ。 プロセスは/bin/echo ep2で、ログにはep2が表示されます。イメージ由来のcmdはなくなり、警告も表示されません。- 両方のキーを設定。 プロセスは
/bin/echo ep2 cmd2です。引数リスト全体を制御できるのはこの場合だけです。
entrypoint を設定するとイメージの CMD が消える理由
イメージの CMD は、そのイメージの ENTRYPOINT に渡すデフォルトの引数リストとして記述されています。entrypoint を置き換えると、それらの引数は実行されなくなったプログラムに属することになります。そのため Compose は、イメージの作成者が意図していないコマンドラインを構成せず、引数を破棄します。docker run --entrypoint も同じように動作します。これは Compose 固有の問題ではなく、Docker の仕様です。
この結果は明確です。nginx:1.27 は ENTRYPOINT ["/docker-entrypoint.sh"] と CMD ["nginx", "-g", "daemon off;"] を宣言しています。entrypoint: /custom-init.sh を設定すると、スクリプトは空の引数リストで開始します。通常の exec "$@" で終わるスクリプトでは、exec する対象がありません。そのため exec は何もせず、スクリプトは最終行まで進み、コンテナはエラーメッセージを出さずに終了コード 0 で終了します。引数は自分で戻してください。
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]覚えておくルールは次のとおりです。entrypoint: を設定する場合は、同じ編集で command: に何を指定するかも決めてください。
Exec 形式と shell 形式、および Compose との違い
Dockerfile では 2 種類の構文を使用できます。CMD ["nginx", "-g", "daemon off;"] は exec 形式です。バイナリが shell を介さずに直接実行されます。CMD nginx -g "daemon off;" は shell 形式です。Docker はこれを /bin/sh -c 'nginx -g "daemon off;"' に書き換えるため、最初に shell が実行され、プログラムはその子プロセスになります。
Compose はこの規則を引き継ぎません。ここが混乱しやすい点です。command: の文字列は引数に分割され、/bin/sh -c のラッパーなしで直接実行されます。Compose のリファレンスにも明記されています。command フィールドは、イメージで定義された SHELL コンテキスト内では実行されません。そのため、shell の機能が必要な場合は、自分で shell を呼び出す必要があります。
そのため、command: echo "hello $$HOSTNAME" はリテラルテキスト hello $HOSTNAME を出力します。文字列を解釈する shell が存在しないため、何も展開されません。shell を使用する場合は、shell を明示的に呼び出します。
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'シグナル、PID 1、正常な docker compose down
docker compose stop と docker compose down は、各コンテナ内の PID 1 に SIGTERM を送信し、stop_grace_period 待機してから SIGKILL を送信します。デフォルトの猶予期間は 10 秒です。
PID 1 は Linux で特別に扱われます。カーネルはシグナルのデフォルト動作を PID 1 に適用しません。そのため、SIGTERM ハンドラーを登録していないプロセスは、PID 1 として実行されると SIGTERM を単に無視します。プロセスは猶予期間いっぱい動作し、その後強制終了されます。この場合、開いている接続や未コミットのトランザクションが失われます。
プログラムの前段にシェルを置くと、この問題が起きやすくなります。シェルが PID 1 になり、多くのシェルは子プロセスにシグナルを転送しないためです。一部のシェルは -c 文字列の最後のコマンドで自身を置き換えるため、その場合はプログラムが PID 1 になることもあります。これはシェルの種類と文字列の内容に依存するため、推測しないでください。実際の状態を確認します。
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoPID 1 がプログラムではなく /bin/sh -c ... と表示される場合は、2 つの対処方法があります。イメージで exec 形式を使用するか、シェルを残したまま exec でプロセスを引き継ぎます。
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'exec は子プロセスを fork せず、シェルプロセスをプログラムに置き換えます。そのため、プログラムが PID 1 を引き継ぎ、シグナルを受信します。
子プロセスを生成しても回収しないプログラムがあります。この場合、PID 1 は子プロセスの回収も担うため、ゾンビプロセスが残ります。Compose には、そのためのスイッチがあります。
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sinit: true は小さな init プロセスを PID 1 として実行します。このプロセスは、プログラムにシグナルを転送し、子プロセスを回収します。stop_grace_period を指定すると、実際にシャットダウンが遅い場合に、より長い時間を確保できます。プログラムが別のシグナルを想定している場合は、stop_signal: SIGQUIT で Compose が送信するシグナルを変更します。イメージがすでに要求している設定は、docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27 で確認できます。
docker compose down がサービスごとに常に 10 秒かかるスタックは、SIGTERM を処理しているプロセスがないことを示しています。ツールを疑う前にこの問題を修正してください。各サブコマンドが削除するものについては、docker compose down と stop の違いを参照してください。
同じ exec 形式と shell 形式の違いは、もう 1 か所にも現れます。test: ["CMD", "curl", "-f", "http://localhost/"] として記述したヘルスチェックはバイナリを直接実行します。一方、test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] はシェル経由で実行されるため、|| が意味を持ちます。このフィールドの詳細は、正しく失敗する Compose ヘルスチェックの記述で説明しています。
公式イメージにフラグを追加する
ここが、多くの読者が知りたい内容です。postgres に追加のフラグを 1 つ指定しつつ、初期化スクリプトは変更したくありません。
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
command: postgres -c max_connections=200 -c shared_buffers=256MB
volumes:
pgdata:変更したのは command: だけなので、docker-entrypoint.sh は引き続き実行され、指定した内容をそのまま exec します。思い込みで判断せず、結果を確認してください。
docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'出力には 200 と表示されます。100 と表示される場合は、docker compose config を実行し、想定した command がマージ後の出力に含まれていることを確認してください。Compose はオーバーライドファイルをマージするとき、command を追加するのではなく、全体を置き換えます。そのため、別のファイルでも command: を設定すると、後の設定が何も通知されずに優先されます。
上記の ${POSTGRES_PASSWORD} は、コンテナが作成される前に、Compose がホスト上の .env ファイルから展開します。その値を安全に保存できる場所については、Compose の環境ファイルと Secret を参照してください。
docker compose run で一度だけマイグレーションを実行する
docker compose run は、同じサービス定義から新しいコンテナを作成し、サービス名の後に入力した内容でコマンドを置き換えます。イメージの entrypoint は引き続き実行されるため、長時間稼働するコンテナとまったく同じ状態に準備されます。
docker compose run --rm app python manage.py migrate--rmは、コマンドの終了時にコンテナを削除します。これを指定しない場合、実行するたびに停止済みコンテナが残り、docker compose ps -aで確認できます。- ポートは公開されません。
runコンテナは--service-portsを追加しない限り、サービスのports:を無視するため、すでに起動しているサービスと競合しません。 - 依存関係にあるサービスが先に起動します。
depends_onに指定されたものはコマンドの実行前に起動し、--no-depsはそれを省略します。 - コンテナには
myproject-app-run-9f2c1aのような生成された名前が付くため、サービスコンテナと名前が競合することはありません。
entrypoint も置き換えるには、専用のフラグを使用します。
docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'結果として得られる引数リストは /bin/sh -c 'python manage.py migrate' です。サービス名の後に指定した語句は、引き続きコマンドとして扱われるためです。docker compose exec はもう一方のツールで、動作が異なります。すでに起動しているコンテナ内でプロセスを実行し、entrypoint: と command: は完全に無視します。新しいコンテナが必要なタスクには run を使用し、実行中のコンテナの内部を確認するには exec を使用します。Compose コマンド早見表では、その他のサブコマンドも並べて比較できます。
コンテナが直ちに終了するのはなぜですか?
まず終了コードを確認してください。原因をすぐに絞り込めます。
docker compose ps -a
docker compose logs app終了コードが 0 で、出力がない場合。 コマンドは実行され、終了しています。最も多い原因は、entrypoint: の上書きによってイメージの CMD も上書きされたことです。その結果、entrypoint に空の引数リストが渡され、引き渡す処理がありませんでした。
permission denied で終わるエラーが出る場合。 イメージ内でスクリプトに実行ビットが設定されていません。通常は、リポジトリ内のファイルにそのビットが設定されていないことが原因です。ビルド時に COPY --chmod=0755 entrypoint.sh /entrypoint.sh で設定してください。
イメージ内に明らかに存在するファイルについて、no such file or directory で終わるエラーが出る場合。 スクリプトの改行コードが Windows 形式です。その先頭行は、キャリッジリターンのバイトを含む #!/bin/sh として解釈されます。そのためカーネルは、そのバイトを名前に含むインタープリターを探し、見つけられません。dos2unix entrypoint.sh を実行し、再発しないように * text eol=lf を .gitattributes に追加してください。
executable file not found in $PATH の場合。 command: に指定されたバイナリがイメージ内にないか、実際のプログラムだけを指定できる場所に cd のようなシェル組み込みコマンドを記述しています。
エントリポイントが失敗するイメージにシェルで入る
何も確認できないうちにエントリポイントが終了する場合は、エントリポイントを置き換えます。
docker compose run --rm --entrypoint /bin/sh appこれが executable file not found in $PATH を返す場合、そのイメージにはシェルがありません。Distroless ベースや scratch ベースのイメージには、シェルが含まれていないことがよくあります。エントリポイントを起動せずに、外部からファイルシステムを読み取ることはできます。
docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probe繰り返し接続できるようにコンテナを起動したままにする場合は、終了しないプロセス上で待機させます。次の内容を、コミットしない override ファイルに記述します。
services:
app:
entrypoint: ["tail", "-f", "/dev/null"]
command: []command: [] は必須ではありません。entrypoint: を設定すると、イメージの CMD はすでに解除されるためです。ただし、これを明記すると、次にファイルを読む人に意図が伝わります。起動して中に入ります。
docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/sh次に、実際のエントリポイントを手動で実行し、どこで停止するかを確認します。これにより、半秒前に終了したコンテナ内ではなく、端末上にエラーメッセージが表示されます。最初のスタックをまだ構築中であれば、VPS 上で最初の Compose スタックを構築する方法で、ここで前提としているファイル構成を説明しています。
FAQ
コンテナが docker compose up の直後に終了するのはなぜですか?
終了コードは docker compose ps -a で確認します。出力がなく終了コードが 0 の場合、通常はサービスに entrypoint: を設定したため、イメージの CMD もクリアされています。その結果、entrypoint が空の引数リストで実行され、終了します。command: で引数を戻します。permission denied で終わるエラーは、entrypoint スクリプトに実行ビットがないことを示します。存在するファイルについて no such file or directory で終わるエラーが出る場合、スクリプトの改行コードが Windows 形式です。そのため、shebang 行が存在しないインタープリターを指定しています。
Compose で entrypoint を設定すると、イメージの CMD は削除されますか?
はい。entrypoint が null 以外の場合、Compose はイメージで宣言されたデフォルトコマンドを無視します。これは文書化された動作であり、docker run --entrypoint と一致します。イメージの CMD は、そのイメージの ENTRYPOINT に渡す引数として記述されるためです。entrypoint を置き換えると、以前の引数は渡し先を失います。新しい entrypoint に引数が必要な場合は、同じサービスで command: を設定します。
Compose の command に指定した文字列は、shell 経由で実行されますか?
いいえ。Dockerfile の CMD とは異なり、Compose の command: に指定した文字列は引数に分割され、/bin/sh -c のラッパーなしで直接実行されます。そのため、$VARIABLE はコンテナ内の shell によって展開されません。shell が必要な場合は、command: /bin/sh -c 'echo "hello $$HOSTNAME"' のように自分で shell を呼び出します。二重の $$ はドル記号をエスケープします。これにより、Compose はホスト上で展開せず、コンテナにそのまま渡します。
1 つのコンテナに対する docker compose down に 10 秒かかるのはなぜですか?
Compose は PID 1 に SIGTERM を送信し、stop_grace_period(デフォルトでは 10 秒)待機してから SIGKILL を送信します。kernel は PID 1 に通常のデフォルトシグナル動作を適用しません。そのため、SIGTERM ハンドラーがないプログラムはシグナルを無視し、常に規定時間いっぱい待機します。実際の PID 1 は docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' ' で確認できます。PID 1 が shell の場合は、イメージを exec 形式に変更するか、shell 文字列内に exec を記述します。プロセスが子プロセスを生成し、それらを回収しない場合は、サービスに init: true を設定します。