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

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > DockerfileのENTRYPOINTとCMDを正しく使い分ける方法|シェル形式・exec形式の設計指針と実践例
「Dockerfileに ENTRYPOINT と CMD を両方書いたのに、docker run で引数を渡しても動きが変わらない」
「ENTRYPOINT と CMD の違いを調べても同じことしか書いていなくて、結局どちらを使えばいいかわからない」

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

この記事のポイント

・ENTRYPOINT は「コンテナが必ず実行するプログラム」を固定する命令
・CMD は「ENTRYPOINT へのデフォルト引数」または単独のデフォルトコマンド
・exec形式(JSON配列)を使わないとシグナルが届かず docker stop が10秒かかる
・両方を組み合わせると「固定コマンド+上書き可能な引数」の柔軟な設計が実現できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も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"]

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

本番環境に近い設計として、シェルスクリプトを 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

本記事のまとめ

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

ENTRYPOINT と CMD を正しく使い分けると、「このコンテナが何をするプログラムか」が Dockerfile だけで明確になります。
シェル形式とexec形式の違いを意識するだけで、本番環境でのシグナル問題も回避できます。
チームで複数のコンテナを管理する場面では、この設計の差が運用の安定性に直結します。

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

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

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

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

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

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

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

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

登録10秒/合わなければ解除3秒 / 詳細はこちら

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

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。