DockerfileのENTRYPOINTとCMDを正しく使い分ける方法|シェル形式・exec形式の設計指針と実践例

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Docker > DockerfileのENTRYPOINTとCMDを正しく使い分ける方法|シェル形式・exec形式の設計指針と実践例
「Dockerfileに ENTRYPOINT と CMD を両方書いたのに、docker run で引数を渡しても動きが変わらない」
「ENTRYPOINT と CMD の違いを調べても同じことしか書いていなくて、結局どちらを使えばいいかわからない」

Dockerfile を書き始めた段階で多くの人がつまずくのが、ENTRYPOINT と CMD の使い分けです。
この記事では、2つの命令の明確な違い、「シェル形式」と「exec形式」の落とし穴、そして実務で役立つ設計パターンを実際のコマンド実行例とともに解説します。
Compose を使った上書き方法、エントリーポイントスクリプトの設計まで含めて、現場で即使える内容をまとめています。
動作確認は Rocky Linux 9 / Ubuntu 24.04 LTS + Docker 26.x で行っています。

この記事のポイント

・ENTRYPOINT は「コンテナが必ず実行するプログラム」を固定する命令
・CMD は「ENTRYPOINT へのデフォルト引数」または単独のデフォルトコマンド
・exec形式(JSON配列)を使わないとシグナルが届かず docker stop が10秒かかる
・Compose では entrypoint: / command: キーで Dockerfile の設定を上書きできる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

ENTRYPOINTとCMDの違いを整理する

Dockerfile には、コンテナ起動時に実行するコマンドを指定する命令が2つあります。

・ENTRYPOINT:コンテナが「必ず実行するプログラム」を定義します。docker run で上書きするには --entrypoint オプションが必要です。
・CMD:ENTRYPOINT が定義されている場合はその「デフォルト引数」として機能します。ENTRYPOINT がない場合は、単独のデフォルト起動コマンドになります。docker run の末尾に引数を渡すだけで上書きできます。

命令 役割 docker run で上書きする方法
ENTRYPOINT コンテナが実行するプログラム(固定) --entrypoint オプションが必要
CMD デフォルト引数またはデフォルトコマンド docker run の末尾に引数を渡すだけ

どちらか1つだけ書く場合と、両方書く場合で動作が変わります。以下で確認しましょう。

1. ENTRYPOINTのみの場合

docker run の末尾に渡したものがすべて ENTRYPOINT の引数になります。

# Dockerfile(ENTRYPOINT のみ指定) FROM ubuntu:22.04 ENTRYPOINT ["echo"]

# 動作確認 $ docker build -t entrypoint-only . $ docker run entrypoint-only hello world hello world # 引数を渡さないと echo コマンドが引数なしで実行される(空行が出力される) $ docker run entrypoint-only

2. CMDのみの場合

docker run の末尾に引数を渡すと、CMD 全体が上書きされます。

# Dockerfile(CMD のみ指定) FROM ubuntu:22.04 CMD ["echo", "Hello from CMD"]

# CMD がそのまま実行される $ docker build -t cmd-only . $ docker run cmd-only Hello from CMD # docker run に引数を渡すと CMD 全体が上書きされる(echo は実行されない) $ docker run cmd-only /bin/bash -c "whoami" root

シェル形式とexec形式の違い

ENTRYPOINT と CMD にはどちらも「シェル形式」と「exec形式」の2通りの記法があります。
この違いを正しく理解しておかないと、本番環境で `docker stop` に10秒以上かかるという問題が起きます。

1. シェル形式(問題あり)

シェル形式はコマンドをそのまま文字列で記述します。

# シェル形式の書き方(スペース区切りの文字列) ENTRYPOINT nginx -g "daemon off;" CMD echo "Hello"

シェル形式では命令が `/bin/sh -c` 経由で実行されます。
そのため、コンテナ内の PID 1(プロセスID 1番)が `/bin/sh` になり、実際のアプリは子プロセスになります。

問題は、`docker stop` が送る `SIGTERM` シグナルが PID 1(sh)に届いても、sh がシグナルを子プロセスへ転送しないことです。
Dockerは10秒後にタイムアウトして `SIGKILL` を送り、コンテナを強制終了します。

# シェル形式の場合、PID 1 が sh になっている $ docker exec mycontainer ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 2888 968 ? Ss 10:05 0:00 /bin/sh -c nginx -g daemon off; root 7 0.0 0.1 54264 4256 ? S 10:05 0:00 nginx: master process nginx -g daemon off; # docker stop がタイムアウトまでかかる(10秒待ってから SIGKILL) $ time docker stop mycontainer mycontainer real 0m10.126s

2. exec形式(推奨)

exec形式はコマンドを JSON 配列で記述します。

# exec形式の書き方(JSON配列) ENTRYPOINT ["nginx", "-g", "daemon off;"] CMD ["echo", "Hello"]

この形式ではプロセスが直接実行され、PID 1 が実際のアプリになります。
`docker stop` の `SIGTERM` がアプリに直接届き、グレースフルシャットダウンが正しく機能します。

# exec形式の場合、PID 1 が nginx になっている $ docker exec mycontainer ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 54264 4256 ? Ss 10:05 0:00 nginx: master process nginx -g daemon off; # docker stop が即座に完了する(SIGTERM が nginx に届く) $ time docker stop mycontainer mycontainer real 0m0.305s

重要:ENTRYPOINT と CMD を組み合わせる場合は、両方 exec形式にする必要があります。
ENTRYPOINT がシェル形式の場合、CMD は完全に無視されます。

ENTRYPOINTとCMDを組み合わせる設計パターン

ENTRYPOINT と CMD を組み合わせると、「実行するプログラムを固定しつつ、デフォルト引数だけ上書き可能」にする設計が実現できます。

典型的な使用例は、CLI ツールのコンテナ化です。

# curl ツールをコンテナ化した Dockerfile FROM ubuntu:22.04 RUN apt-get update && apt-get install -y curl --no-install-recommends \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT ["curl"] CMD ["--help"]

$ docker build -t my-curl . # 引数なし → CMD の --help が使われる $ docker run my-curl Usage: curl [options...] -d, --data HTTP POST data ... # URL を指定 → CMD が上書きされて指定の URL にリクエストする $ docker run my-curl https://example.com Example Domain ... # JSON のレスポンスを整形して取得する $ docker run my-curl -s https://api.example.com/status | python3 -m json.tool { "status": "ok", "version": "1.2.3" }

この設計のポイントを整理します。

・ENTRYPOINT で「何をするコンテナか」を固定する:curl コンテナに /bin/bash を起動させるといったことが docker run の末尾引数だけではできなくなります
・CMD で「デフォルト動作」を定義する:引数なしで実行したときのフォールバックとして機能します
・docker run の末尾引数で CMD を柔軟に上書きできる:--entrypoint オプションを使う必要がなく、シンプルに使えます

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、DockerfileのベストプラクティスやCompose設計まで含めた実践的なDocker講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>

docker runでENTRYPOINT・CMDを上書きする方法

1. CMDを上書きする(末尾に引数を渡す)

docker run の末尾に渡した引数がそのまま CMD を上書きします。
ENTRYPOINT は変わらず、引数だけ差し替えられます。

# CMD を上書きして別のオプションを渡す $ docker run my-curl -I https://example.com HTTP/2 200 content-type: text/html; charset=UTF-8 ...

2. ENTRYPOINTを上書きする(--entrypoint)

ENTRYPOINT 自体を変更するには `--entrypoint` オプションを使います。
デバッグ時にコンテナ内のシェルに入りたい場合によく使います。

# ENTRYPOINT を bash で上書きしてコンテナ内に入る $ docker run --entrypoint /bin/bash -it my-curl root@a1b2c3d4e5f6:/# # コンテナ内から curl が使えるか確認する root@a1b2c3d4e5f6:/# curl --version curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 ...

3. docker-compose.yml での上書き

Compose を使っている場合は、`entrypoint:` と `command:` キーで上書きできます。

・entrypoint:ENTRYPOINT に相当します。記述すると Dockerfile の ENTRYPOINT を上書きします
・command:CMD に相当します。記述すると Dockerfile の CMD を上書きします

# docker-compose.yml での上書き例 services: curl: image: my-curl # コンテナを起動したまま待機させてデバッグに使う entrypoint: ["/bin/bash", "-c"] command: ["while true; do sleep 60; done"]

4. docker compose run でENTRYPOINTを上書きしてシェルに入る

Compose プロジェクトで本番イメージの環境をシェルで確認したいときは、`docker compose run --rm --entrypoint bash` が便利です。
compose.yml に設定した環境変数・ボリューム・ネットワークを引き継いだまま、ENTRYPOINT だけを bash に切り替えられます。
サービスが起動していない状態でも実行できる点が、次に紹介する `docker compose exec` との大きな違いです。

# ENTRYPOINTをbashに上書きしてコンテナ内に入る # compose.ymlの environment / volumes / networks 設定は引き継がれる $ docker compose run --rm --entrypoint bash web root@f2a3b4c5d6e7:/app# env | grep APP APP_ENV=production APP_VERSION=1.2.0 # 確認が終わったら exit(--rm でコンテナは自動削除される) root@f2a3b4c5d6e7:/app# exit

デプロイ前の設定ファイル確認、依存ライブラリのバージョン確認など、「本番イメージに入って中を見たいが、アプリは起動したくない」場面で使ってください。

5. docker compose exec で稼働中のコンテナに入る

`docker compose up` で起動済みのコンテナ内でコマンドを実行するには `docker compose exec` を使います。
新しいコンテナを作らず、起動中のコンテナにプロセスを追加する点が `docker compose run` との違いです。

# 稼働中の web サービスにシェルで入る $ docker compose exec web bash root@a1b2c3d4e5f6:/app# # Dockerfile で USER が設定されていても root として入る(--user オプション) $ docker compose exec --user root web bash root@a1b2c3d4e5f6:/app# id uid=0(root) gid=0(root) groups=0(root) # ワンライナーでコンテナ内のコマンドを実行する $ docker compose exec web python manage.py showmigrations

コンテナ ID ではなくサービス名で指定できるのが `docker exec` との実用上の利点です。
コンテナ ID は `docker compose up` のたびに変わりますが、サービス名は常に固定です。

実践:エントリーポイントスクリプトを使うパターン

本番環境に近い設計として、シェルスクリプトを ENTRYPOINT にするパターンを紹介します。
この手法は公式の Docker イメージ(PostgreSQL, MySQL, Redis など)でも広く使われています。

1. スクリプトを使う理由

直接アプリを ENTRYPOINT に指定するだけでは、コンテナ起動前の初期化処理が行えません。
エントリーポイントスクリプトを挟むことで、次の処理をコンテナ起動時に安全に実行できます。

・環境変数の検証:必須の環境変数が未設定ならエラーで停止する
・設定ファイルの動的生成:envsubst などで設定ファイルのプレースホルダーを置換する
・起動順序の制御:データベースが起動するまでリトライするなど

2. エントリーポイントスクリプト

#!/bin/bash # docker-entrypoint.sh set -e # 必須の環境変数を検証する if [ -z "$APP_ENV" ]; then echo "ERROR: APP_ENV が未設定です。起動を中止します。" >&2 exit 1 fi echo "INFO: APP_ENV=${APP_ENV} でサーバーを起動します" # envsubst で設定テンプレートの変数を置換する envsubst '${APP_ENV} ${SERVER_NAME}' \ < /etc/nginx/nginx.conf.template \ > /etc/nginx/nginx.conf # exec で CMD(ENTRYPOINT の後続引数)を実行する # exec を使うことで nginx が PID 1 を引き継いでシグナルを受け取れる exec "$@"

スクリプト末尾の `exec "$@"` が最重要のポイントです。
`exec` を書くことで、このスクリプトが CMD(ここでは `nginx -g "daemon off;"`)に置き換えられ、nginx が PID 1 を引き継ぎます。
`exec` を書き忘れると、シェルが PID 1 のまま残り、nginx へのシグナルが届かなくなります。

3. Dockerfile

FROM nginx:1.27-alpine # envsubst のために gettext をインストールする RUN apk add --no-cache gettext # エントリーポイントスクリプトをコピーして実行権限を付与する COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh # 設定ファイルのテンプレートをコピーする COPY nginx.conf.template /etc/nginx/nginx.conf.template # スクリプトを ENTRYPOINT に設定する(exec形式必須) ENTRYPOINT ["docker-entrypoint.sh"] # デフォルトでは nginx をフォアグラウンド実行する CMD ["nginx", "-g", "daemon off;"]

4. 動作確認

$ docker build -t my-nginx . # 正常起動(APP_ENV を設定) $ docker run -d \ -e APP_ENV=production \ -e SERVER_NAME=www.example.com \ -p 8080:80 \ --name web \ my-nginx INFO: APP_ENV=production でサーバーを起動します ... # 環境変数なし → エラーで起動を停止する $ docker run my-nginx ERROR: APP_ENV が未設定です。起動を中止します。 # graceful stop が機能しているか確認する(即座に停止する) $ time docker stop web web real 0m0.318s

よくあるミスとトラブルシュート

【問題1】docker stop に10秒以上かかる

原因:ENTRYPOINT がシェル形式で記述されており、PID 1 が sh になっている。
対処:ENTRYPOINT をexec形式(JSON配列)に変更する。シェルスクリプトを ENTRYPOINT にしている場合は末尾に `exec "$@"` があるか確認する。

# NG: シェル形式(PID 1 が sh になる) ENTRYPOINT nginx -g "daemon off;" # OK: exec形式(PID 1 が nginx になる) ENTRYPOINT ["nginx", "-g", "daemon off;"]

【問題2】ENTRYPOINT と CMD を書いたのに CMD が無視される

原因:ENTRYPOINT がシェル形式で記述されている。シェル形式の ENTRYPOINT は CMD を完全に無視します。
対処:ENTRYPOINT をexec形式に変更する。

# NG: ENTRYPOINT がシェル形式 → CMD は無視される ENTRYPOINT echo CMD ["Hello"] # 無視される(echo が引数なしで実行される) # OK: 両方 exec形式 ENTRYPOINT ["echo"] CMD ["Hello"] # "Hello" が echo に渡されて "Hello" と出力される

【問題3】docker run に引数を渡しても反映されない

原因:docker-compose.yml に `command:` が明示的に記述されている。`command:` キーは Dockerfile の CMD を上書きするため、docker run の末尾引数よりも Compose の設定が優先されます。
対処:Compose ファイルの `command:` 行を確認し、不要であれば削除する。

【問題4】エントリーポイントスクリプトが「Permission denied」で起動しない

原因:スクリプトに実行権限がない、またはスクリプトの先頭行(shebang)が Windows 改行コード(CRLF)になっている。
対処:Dockerfile で `chmod +x` を実行しているか確認する。改行コードは LF に統一する。

# Dockerfile で実行権限を付与する(必須) COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh # Windows で作成したスクリプトの改行コードを LF に変換する $ sed -i 's/\r//' docker-entrypoint.sh # スクリプトの先頭が正しい shebang になっているか確認する $ head -1 docker-entrypoint.sh #!/bin/bash

【問題5】CIスクリプトから docker compose exec を実行すると「the input device is not a TTY」が出る

原因:CI 環境(GitHub Actions・Jenkins 等)では疑似 TTY(tty)が割り当てられないため、`docker compose exec` がデフォルトで TTY を要求してエラーになります。
対処:`-T` オプションを追加して疑似 TTY を無効化する。ただし `-T` を使うとインタラクティブな入力が必要なコマンドは動作しないため、バッチ処理・ワンライナー実行に限定してください。

# CI から実行するとエラーになるパターン $ docker compose exec web python manage.py migrate the input device is not a TTY # 修正後(-T を追加して TTY を無効化) $ docker compose exec -T web python manage.py migrate Operations to perform: Apply all migrations: accounts, admin, auth Running migrations: Applying accounts.0001_initial... OK # コンテナの出力をパイプで受け取る場合も -T が必要 $ docker compose exec -T db pg_dump -U postgres myapp | gzip > backup.sql.gz

本記事のまとめ

ENTRYPOINT と CMD の使い分けポイントをまとめます。
やりたいこと Dockerfile / コマンドの書き方
コマンドを固定して引数だけ上書き可能にする ENTRYPOINT ["cmd"] + CMD ["default-arg"]
シグナルを正しく受け取れるようにする exec形式(JSON配列)で記述する
コンテナ起動前に初期化処理を実行する シェルスクリプトを ENTRYPOINT にして末尾で exec "$@"
docker run でプログラム自体を変更する docker run --entrypoint /bin/bash イメージ名
docker run でデフォルト引数だけ変更する docker run イメージ名 新しい引数
Compose でサービスの CMD を上書きする compose.yml の command: キーに値を設定する
Compose でENTRYPOINTを上書きしてシェルに入る docker compose run --rm --entrypoint bash サービス名
稼働中コンテナにシェルで入る docker compose exec サービス名 bash
CIから exec を実行してコマンド結果をパイプで受け取る docker compose exec -T サービス名 コマンド

ENTRYPOINT と CMD を正しく使い分けると、「このコンテナが何をするプログラムか」が Dockerfile だけで明確になります。
シェル形式とexec形式の違いを意識するだけで、本番環境でのシグナル問題も回避できます。
Compose を使う場面では `docker compose run --entrypoint` と `docker compose exec` の使い分けも覚えておくと、デバッグと本番運用の両方で迷わなくなります。

次に読む記事:
・Dockerfileの書き方入門|自分のアプリをイメージ化する基礎と注意点
・docker execコマンドでコンテナ内を調査・デバッグする方法|docker logs・docker inspectの実践例も

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、DockerfileのENTRYPOINT設計から本番運用まで含めた実践的なDocker講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>

<PR>手元に置いて学びを深める1冊

Docker/Kubernetes実践コンテナ開発入門 改訂新版

DockerfileのENTRYPOINT・CMD設計からコンテナ本番運用まで体系的に学べる一冊。記事で解説した概念をより深く掘り下げたい方に。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。