Dockerコンテナが起動直後に止まり、`Exited (0)` と表示される場合、まず疑うべきは「クラッシュ」ではなく、コンテナのメインプロセスが正常終了したことです。Dockerは`ENTRYPOINT`や`CMD`で起動したメインプロセスが動いている間だけコンテナを実行します。`docker ps -a`で状態を確認し、`docker inspect`でExitCodeと実行コマンド、`docker logs`で標準出力を確認すると、原因を短時間で切り分けられます。
まず結論:exit code 0は「正常終了」でもコンテナは止まる
| 状態 | 意味 | 最初に確認すること |
|---|---|---|
| Exited (0) | メインプロセスが正常終了した | CMD / ENTRYPOINTが長時間動く処理か |
| Exited (1)など非0 | アプリやコマンドがエラー終了した可能性が高い | docker logsとExitCode |
| 起動→即停止を繰り返す | restart policyで再起動されている可能性 | RestartPolicyとログ |
| 起動中だがサービスへ繋がらない | コンテナ停止とは別問題 | port・listen address・healthcheck |
Docker公式では、コンテナのmain running processはDockerfile末尾の`ENTRYPOINT`や`CMD`で決まると説明されています。つまり、HTTPサーバーを起動したつもりでも、実際のメインコマンドが一瞬で終わればコンテナも終了します。
最初に3コマンドで状態を切り分ける
docker ps -a
docker inspect my-container --format '{{.State.Status}} exit={{.State.ExitCode}} cmd={{json .Config.Cmd}} entrypoint={{json .Config.Entrypoint}}'
docker logs my-container
`docker ps -a`で停止状態、`docker inspect`でExitCodeと起動設定、`docker logs`でアプリの標準出力・標準エラーを確認します。ExitCodeが0でログにもエラーがなければ、「実行したコマンドが完了する設計になっていないか」を優先して見ます。
用語解説:PID 1
コンテナ内のメインプロセスです。Dockerはこのプロセスのライフサイクルをコンテナの実行状態として扱います。
原因1:CMDが「すぐ終わるコマンド」になっている
Dockerfileの`CMD`は、イメージを起動したときのデフォルトコマンドです。`echo`や一回だけ実行するスクリプトを`CMD`にすると、その処理が終わった時点でコンテナも正常終了します。
FROM alpine:latest
CMD ["sh", "-c", "echo ready"]
このイメージは`ready`を出力すると役目を終えるため、`Exited (0)`になるのが正しい挙動です。Webサーバー用途なら、サーバープロセス自体がフォアグラウンドで動き続けるようにします。
FROM python:3.13-slim
WORKDIR /app
CMD ["python", "-m", "http.server", "8000"]
原因2:サービスをバックグラウンドへ送って親プロセスが終了している
起動スクリプト内でサーバーを`&`でバックグラウンド実行し、その後スクリプト本体が終わる構成も要注意です。コンテナでは、長時間動かしたいサービスをメインプロセスとしてフォアグラウンド実行するのが基本です。
#!/bin/sh
my-server &
echo "started"
この例ではshellが最後まで到達すると終了します。サービス側にforeground optionがあるならそれを使い、起動スクリプトを使う場合も最終的なアプリを`exec`で置き換える設計にすると、シグナル処理も分かりやすくなります。
#!/bin/sh
set -e
# setup commands...
exec my-server --foreground
原因3:docker runやComposeでCMDを上書きしている
Dockerfileの`CMD`は実行時の引数で上書きできます。イメージではサーバーを起動する設定でも、`docker run image echo ok`のような起動をすると、`CMD`の代わりに`echo ok`が実行され、すぐ終了する構成があります。Composeの`command:`も同じ切り分け対象です。
docker image inspect my-image --format 'cmd={{json .Config.Cmd}} entrypoint={{json .Config.Entrypoint}}'
docker container inspect my-container --format 'cmd={{json .Config.Cmd}} entrypoint={{json .Config.Entrypoint}}'
イメージ側のデフォルトと、実際に作成されたコンテナの設定を見比べると、どこで上書きされたか判断しやすくなります。
restart policyで隠す前に終了理由を直す
`–restart=on-failure`は、プロセスが非0のExitCodeで失敗した場合に再起動する方針です。ExitCode 0の正常終了では対象になりません。`always`や`unless-stopped`は停止後の再起動を行えますが、起動コマンドが一瞬で終わる設計そのものは直りません。
| restart policy | ExitCode 0で再起動 | 用途 |
|---|---|---|
| no | しない | デフォルト。まず原因切り分けに向く |
| on-failure | しない | エラー終了時だけ再試行したい |
| always | する | 停止後も原則再起動したい |
| unless-stopped | する | 手動停止を尊重しつつ再起動したい |
起動直後の正常終了をrestart policyだけでループさせるより、`CMD`・`ENTRYPOINT`・起動スクリプトを先に確認する方が根本原因を特定できます。
切り分け順は「ExitCode→実行コマンド→ログ→PID 1」
- `docker ps -a`でExited状態と終了タイミングを見る
- `docker inspect`で`.State.ExitCode`を確認する
- `docker logs`でアプリが正常終了した理由やエラーを確認する
- `CMD`・`ENTRYPOINT`・Composeの`command`上書きを確認する
- サーバーがforegroundで動き続ける構成か確認する
- 必要な場合だけrestart policyを設定する
(Docker自体の基本構造を整理したい場合については『Dockerとは?初心者向け:基礎から実践まで完全ガイド』をご参照ください)
(Spring BootをDockerで動かす構成も確認したい場合については『Docker + Spring Boot で手軽に開発環境を構築しよう!』をご参照ください)
よくある質問
exit code 0なら問題はないですか?
コマンドとしては正常終了ですが、常駐サーバーとして動かしたかった場合は設定上の問題があります。期待するアプリが動き続けるべきかどうかで判断します。
tail -f /dev/nullでコンテナを止めない方法は有効ですか?
調査目的でコンテナを維持する用途には使えますが、アプリ本来のメインプロセスを正しく動かす修正にはなりません。本番設計では実際のサービスをPID 1として動かす方が意図を明確にできます。
on-failureを付けても再起動しないのはなぜですか?
`on-failure`は非0のExitCodeで終了した場合に再起動する方針です。ExitCode 0は正常終了なので対象外です。
まず`docker inspect`でExitCodeと`CMD`・`ENTRYPOINT`を並べて確認し、コンテナが実際に何をPID 1として実行しているかを特定してみてください。