Docker Compose の 'service is unhealthy' エラーを修正する

intermediate🐳 Docker2026-03-17| Docker Desktop / Docker Engine 20.x+、Docker Compose v2.x、Linux/macOS/Windows

Error Message

service "myservice" is unhealthy
#docker#docker-compose#healthcheck#container

エラーの概要

docker compose up を実行すると、処理が完全に止まってしまいます。

Error response from daemon: service "myservice" is unhealthy

または、上流のサービスが正常状態にならないために、依存するコンテナが起動を拒否します。どちらにせよ、行き詰まってしまいます。

何が起きているか:Docker がコンテナ内でヘルスチェックコマンドを実行し、ゼロ以外の終了コードを返し続けました。リトライ上限に達すると、Docker はコンテナに unhealthy のスタンプを押します。Compose の設定によっては、コンテナを強制終了するか、depends_on しているすべてのサービスをブロックします。

原因

  • ヘルスチェックコマンド自体が間違っている — パス、ツール、ポートが誤っている
  • サービスの初期化が start_period の許容時間より長くかかる
  • コンテナ内でサービス自体が壊れている(設定ミス、クラッシュループ)
  • ヘルスチェックが接続する依存関係(DB や外部 API)がまだ準備できていない
  • ヘルスチェックツールがイメージにインストールされていない(例:curlwget がない)

ステップ 1:Docker が実際に何を見ているか確認する

推測はやめて、まずヘルスチェックの生の出力を取得しましょう。

# ヘルスの状態と最後のチェック出力を表示
docker inspect --format='{{json .State.Health}}' myservice | jq .

Log 配列が重要な部分です。各エントリには ExitCodeOutput があります。これがヘルスチェックコマンドの実際の stdout/stderr です。ほとんどの場合、これで何が壊れているかが正確にわかります。

# コンテナが実行中かどうか確認
docker compose ps
docker compose logs myservice

修正 1:ヘルスチェックコマンドを修正する

最も一般的な原因:呼び出しているツールがイメージに入っていない、またはエンドポイントが間違っています。ポート 3000 の Node.js API で動作するヘルスチェックの例:

services:
  api:
    image: my-node-app
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s

イメージで使えるツールが不明な場合は、直接テストしてみましょう。

docker exec myservice wget -qO- http://localhost:3000/health
docker exec myservice curl -f http://localhost:3000/health

PostgreSQL には組み込みの pg_isready を使いましょう。追加ツール不要です:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 5s
  timeout: 3s
  retries: 5

Redis はさらにシンプルです:

healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 5s
  timeout: 3s
  retries: 5

修正 2:start_period で起動が遅いサービスに余裕を持たせる

Java アプリや Spring Boot ではこの問題がよく発生します。Spring Boot サービスは完全に起動するまで 30〜90 秒かかることがあります。start_period が十分でないと、ロードが終わる前に Docker が unhealthy とマークしてしまいます。

重要な点:start_period 中に失敗したチェックは retries にカウントされません。これは猶予期間であり、カウントダウンタイマーではありません。

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
  interval: 15s
  timeout: 10s
  retries: 5
  start_period: 60s   # リトライがカウントされる前に 60 秒の猶予を与える

コンテナのログで実際の起動時間を確認し、start_period をその値に 10〜15 秒のバッファを加えた値に設定してください。

修正 3:depends_on で condition: service_healthy を使う

デフォルトでは、depends_on はコンテナが起動するのを待つだけで、準備完了を待ちません。データベースコンテナが「起動」してから Postgres が実際に接続を受け付けるまで 5 秒かかることがあります。アプリが早すぎるタイミングで接続しようとして失敗します。

修正は依存関係ごとに 1 行追加するだけです:

services:
  app:
    image: my-app
    depends_on:
      db:
        condition: service_healthy   # db がヘルスチェックを通過するまで待機
      redis:
        condition: service_healthy

  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

  redis:
    image: redis:7
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3

これにより、dbredis の両方が正常であることが確認されるまで app は起動しません。

修正 4:ヘルスチェックを一時的に無効にする

問題を切り分ける必要があるときに便利です。ヘルスチェックの設定が壊れているのか、それともサービス自体がクラッシュしているのかを素早く判断できます:

services:
  myservice:
    image: my-image
    healthcheck:
      disable: true

ヘルスチェックを無効にするとコンテナが正常に動くが、有効にすると失敗する場合、ヘルスチェックの設定が問題です。本番環境ではこの設定のままにしないでください。

修正 5:サービス自体が壊れている場合

ヘルスチェックが完璧に正しい設定であることもあります。コンテナ自体が本当に失敗しているのです。ログを調査しましょう:

docker compose logs --tail=100 myservice

# 起動してリアルタイムでエラーを監視
docker compose up 2>&1 | grep -E "(error|Error|fatal|Fatal|unhealthy)"

よくある原因:環境変数の欠如、誤ったデータベース認証情報、ホスト上のポートの衝突、Docker が書き込めないパーミッションのボリュームマウントなど。

確認

修正を適用したら、コンテナのステータスをリアルタイムで確認しましょう:

watch -n2 'docker compose ps'

STATUS 列が設定した start_period + (interval × retries) の時間内に startinghealthy に変わるはずです。inspect で二重確認しましょう:

docker inspect --format='{{.State.Health.Status}}' myservice
# 期待される出力: healthy

予防策

  • まず docker exec でコマンドをテストする — compose.yml に記述する前に、コンテナ内でヘルスチェックコマンドを手動で実行してください。試行錯誤の時間を大幅に節約できます。
  • 常に start_period を明示的に設定する — デフォルトは 0s で、コンテナ起動直後にチェックが実行されます。非自明なサービスのほぼすべてで、少なくとも 10〜30 秒が必要です。
  • 汎用の TCP プローブより具体的なチェックを優先するpg_isreadyredis-cli ping は失敗時に意味のある出力を返します。生の TCP チェックはポートが開いているだけを確認し、サービスが動作しているかどうかはわかりません。
  • すべてのステートフルな依存関係にヘルスチェックを追加する(データベース、キャッシュ、メッセージキュー)し、depends_oncondition: service_healthy と組み合わせてください。
  • CI ログにヘルスチェックの出力を記録する — タイミングの不安定な問題は、深夜 2 時の本番インシデントよりもパイプラインでの方がはるかに発見しやすいです。

Related Error Notes