docker『Container Exited with Code』の原因と対処法

docker『Container Exited with Code』の原因と対処法

コンテナが即終了・異常終了・シグナルによる終了を示すステータス。まず「終了コード」「直前ログ」「エントリーポイント」「リソース制限」「シグナル(SIGKILL/SIGTERM)」を突き合わせ、原因を特定してから再実行条件や設定を修正する。

終了コードの読み方(代表パターン早見)

# 0   : 正常終了(プロセス完了)。常駐想定のコンテナが0で即終了→CMD/ENTRYPOINTの想定違い。
# 1   : 一般的な異常終了。アプリの起動失敗や設定エラー。
# 2   : シェルの誤用、引数エラー(bashの内規)。
# 126 : 実行権限なし(Permission denied)や実行不可ファイルを起動。
# 127 : コマンドが見つからない(PATH違い・ファイル欠如)。
# 128+n: シグナルnで終了(128+9=137など)。
# 137 : SIGKILL(多くはOOMKilled)。メモリ不足の典型。
# 139 : セグフォ(SIGSEGV)。ネイティブクラッシュ。
# 143 : SIGTERMで正常終了(docker stopの既定シグナル)。
# 255 : 異常終了の最終手段(戻り値域外)や強制終了。

まず行う最短切り分け(3コマンド)

# 1) 直近の終了コードと状態
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.RunningFor}}'

# 2) 直前ログ(標準出力/標準エラー)
docker logs --tail=200 <container>

# 3) デバッグ用シェルで起動(ENTRYPOINTを無効化)
docker run --rm -it --entrypoint sh <image>:<tag>

CMD/ENTRYPOINTの誤り(0で即終了・127/126が頻発)

# ENTRYPOINT/CMDの定義を見直す
docker image inspect <image>:<tag> --format '{{json .Config}}' | jq '.Entrypoint,.Cmd'

# Shell形式よりexec形式を推奨(シグナル伝播も良好)
# NG: シェル解釈に依存し、引数・シグナル伝播で事故が起きやすい
CMD node server.js

# OK: exec形式でPID 1にプロセスを置く
CMD ["node","server.js"]

# 実行ビット不足で126 → 実行権限を付与して再ビルド
RUN chmod +x /app/start.sh

依存ファイル/PATH不足(127: command not found)

# 依存バイナリの所在確認
docker run --rm <image> sh -lc 'which yourcmd || echo "not found"; echo $PATH'

# ビルドでPATHを汚していないか(上書き→実行ファイルを見失う)
ENV PATH="/usr/local/bin:/usr/bin:/bin"

OOMKilled/メモリ不足(137)

# イベントでOOMを確認(Docker)
docker inspect <container> --format '{{json .State}}' | jq '.OOMKilled,.ExitCode'

# 制限値の見直し(過度な制限は避ける)
docker run -m 1g --memory-swap 1g <image>     # swapを同値で実質無効にしない
docker run -m 1g --memory-swap 2g <image>     # 余裕を持たせる

# アプリ側:ワーカー数/キャッシュ/ヒープ上限を下げる
# Node.js 例
node --max-old-space-size=512 server.js

シグナル終了(143=SIGTERM, 137=SIGKILL)とグレースフル停止

# docker stop は SIGTERM → 猶予(既定10s) → SIGKILL
docker stop <container>
docker stop -t 30 <container>   # 猶予を延ばす

# PID 1 問題に備えてtiniを導入(ゾンビ化/シグナル伝播改善)
# Dockerfile
FROM alpine:3.20
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini","--"]
CMD ["your-app","--serve"]

実行ユーザーと権限(126/Permission deniedや実行失敗)

# 実行ユーザーを確認
docker run --rm <image> id

# DockerfileでUSERを固定し、必要な可読/実行権を付与
RUN adduser -D app && mkdir -p /app && chown -R app:app /app
USER app

環境変数・設定ファイル不足(Exit 1の定番)

# 実行前に必須変数を検証するエントリスクリプト例
#!/bin/sh
set -eu
: "${DB_HOST:?DB_HOST is required}"
: "${DB_USER:?DB_USER is required}"
exec your-app
# Dockerfile
COPY entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

ヘルスチェックと再起動ポリシー(CrashLoop対策)

# Dockerfile
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget -qO- http://localhost:8080/health || exit 1

# 再起動ポリシーで自動復旧(原因が外的要因で一時的なら有効)
docker run --restart=on-failure:5 <image>
docker run --restart=always <image>

ログ/コアダンプ/アプリの終了コードを可視化

# アプリ側で終了コードを明示
process.on('uncaughtException', e => { console.error(e); process.exit(1); });
process.on('SIGTERM', () => { console.log('graceful'); process.exit(0); });

# コアダンプを有効化(検証環境のみ推奨)
ulimit -c unlimited

ボリューム/マウント起因の失敗(設定・権限・存在チェック)

# 起動時に必要なファイル/ディレクトリの存在を確認
docker run --rm -v $(pwd)/conf:/app/conf <image> sh -lc 'ls -l /app/conf && test -f /app/conf/app.yml'

# 読取専用マウントで書込→Exit 1
docker run -v /data:/app/data:ro <image> sh -lc 'echo x > /app/data/a'  # 失敗

KubernetesでのExit Code(イベント/状態から特定)

# Podの状態・イベントを確認
kubectl describe pod <pod> -n <ns> | sed -n '/Containers:/,/Events:/p'
kubectl get pod <pod> -n <ns> -o json | jq '.status.containerStatuses[] | {name,state,lastState}'

# OOMKilled/BackOff/CrashLoopBackOffの切り分け
kubectl logs <pod> -n <ns> --previous --all-containers

最小再現テンプレ(ENTRYPOINTを差し替えて切り分け)

# 1) 同一イメージをシェルで起動して依存確認
docker run --rm -it --entrypoint sh <image> -lc '
  which your-app || exit 127
  test -f /app/config.yml || exit 2
  your-app --version || exit 1
'

# 2) 引数/環境の違いで再現するか
docker run --rm -e ENV=dev <image> your-app --config /app/config.yml

NG→OK早見(クイック修正)

# NG: shell形式CMD → OK: exec形式CMDでシグナル伝播改善
CMD ["your-app","--serve"]

# NG: 実行ビットなし → OK: chmod +x
RUN chmod +x /usr/local/bin/start.sh

# NG: PATH/依存欠如 → OK: which/lddで検証し、Dockerfileで追加
RUN apk add --no-cache curl

# NG: すぐOOM → OK: メモリ制限/ワーカー数を調整
docker run -m 1g --memory-swap 2g <image>

# NG: stopで強制KILL → OK: Tini導入+-t猶予延長
ENTRYPOINT ["/sbin/tini","--"]
docker stop -t 30 <container>

チェックリスト(上から順に潰す)

1) 終了コードを確認(0/1/126/127/137/143等)
2) docker logs と直近イベントで直前のメッセージを読む
3) ENTRYPOINT/CMDの形式と実体コマンドの有無を確認(exec形式推奨)
4) 必須環境変数/設定ファイルの存在を検証
5) メモリ/OOM(137)やシグナル(143/137)の発生源を特定
6) 実行ユーザー/権限/マウントオプション(:ro, SELinux)を点検
7) ヘルスチェックと再起動ポリシーを適用(外的要因なら有効)
8) それでも不明ならENTRYPOINTをshに差し替えて最小再現