言語を切り替える
テーマを切り替える

Docker Compose のサービス依存:ヘルスチェックで DB 起動順の問題を解決

金曜の夜 10 時、ターミナルに同じエラーログが何度も流れている。

アプリコンテナは延々と再起動し、DB コンテナは起動しているのに、いつも一歩遅い。docker-compose.yml を確認する——depends_on は設定済みなのに、なぜ動かない?

多くの開発者が一度は踏む問題です。ローカルで docker-compose up すると、最初の 1〜2 回は必ず失敗し、10 秒ほど待って何度か再起動してようやく正常に動く。

原因はシンプルです。Docker の depends_on はコンテナの起動順序だけを管理し、サービスが本当に準備完了(Readiness)したかまでは保証しない

本記事では、次の内容を手順どおりに解説します。

  • depends_on の 3 つの condition 設定(9 割の人がデフォルトしか知らない)
  • PostgreSQL / MySQL の正しい healthcheck の書き方(完全な設定例付き)
  • wait-for-it スクリプトに頼らないモダンな方法
  • コンテナ起動を安定させるトラブルシューティングリスト

なぜ depends_on だけでは不十分か:起動 ≠ 準備完了

Docker 公式ドキュメントに、見落としがちな重要な一文があります。

Compose does not wait until a container is “ready”, only until it’s running.

つまり Compose はコンテナが走り始めれば OK で、サービスが本当に使えるかまでは待ちません。

コンテナ起動とサービス準備完了のタイムラグ

PostgreSQL コンテナの起動過程を想像してください。

  1. 0 秒:Docker がコンテナを起動、postgres プロセス開始 ← depends_on はここで GO サイン
  2. 2 秒:データディレクトリ初期化
  3. 5 秒:設定ファイル読み込み
  4. 8 秒:init スクリプト実行(あれば)
  5. 12 秒:ようやく ready、接続受付開始

この間に 12 秒のギャップがあります。Web アプリが 1 秒目に DB 接続を試みれば、結果は必ず「Connection refused」。

実際のケースはもっと極端です。以前メンテしていたレガシープロジェクトでは、DB 初期化スクリプトで 500MB のテストデータをインポートするだけで 40 秒かかりました。デフォルトの depends_on 設定では、アプリコンテナが DB に接続できるまで最低 5 回クラッシュして再起動していました。

depends_on の 3 つの condition

実は depends_on には 3 つの condition があります。

services:
  web:
    depends_on:
      db:
        condition: service_started  # デフォルト。コンテナ起動で OK
        # condition: service_healthy  # ヘルスチェック通過を待つ
        # condition: service_completed_successfully  # 正常終了を待つ(init コンテナ向け)

service_started(デフォルト):コンテナが running 状態になれば次へ進む。これが depends_on を設定しても問題が起きる理由です。

service_healthy:healthcheck が通過し、コンテナ状態が「healthy」になるまで待つ。これが本当に必要な設定です。

service_completed_successfully:コンテナが正常終了(exit code 0)するまで待つ。データ移行などのワンショットタスク向け。

なぜデフォルトが service_healthy ではないのか

service_healthy がこんなに便利なら、なぜデフォルトではないのでしょうか。

理由は 2 つあります。

  1. すべてのサービスにヘルスチェックが必要とは限らない(ステートレスな worker など)
  2. ヘルスチェックは自分で設定する必要があり、Docker はサービスの「準備完了」の定義を知らない

次は、ヘルスチェックの設定方法です。

ヘルスチェック(healthcheck)完全設定ガイド

healthcheck の原理はシンプルです。Docker が定期的にコマンドを実行し、0 が返れば healthy、1 なら unhealthy と判定します。

完全な healthcheck 設定

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]  # チェックコマンド
      interval: 10s       # 10 秒ごとにチェック
      timeout: 5s         # 1 回のチェックは 5 秒でタイムアウト
      retries: 3          # 3 回連続失敗で unhealthy
      start_period: 30s   # 起動後 30 秒以内の失敗は retries にカウントしない

5 つのパラメータ、それぞれ重要です。順番に見ていきましょう。

test:チェックコマンド

形式は 2 つあります。

# 方式 1:shell を使う(推奨)
test: ["CMD-SHELL", "pg_isready -U postgres"]

# 方式 2:コマンドを直接実行(shell 経由なし)
test: ["CMD", "pg_isready", "-U", "postgres"]

大半の場合は CMD-SHELL で十分です。パイプやリダイレクトなど shell 機能が使えます。

よくある落とし穴:チェックコマンドで使うツールがイメージ内に存在する必要があります。HTTP インターフェースを curl で確認しようとしても、イメージに curl がなければ healthcheck は常に失敗します。以前 Dockerfile に RUN apk add curl を 1 行足すまで、30 分ハマったことがあります。

interval:チェック間隔

どのくらいの頻度でチェックするか。頻繁すぎるとリソースを無駄にし、遅すぎると反応が鈍くなります。

  • 10 秒は多くのシーンで妥当なデフォルト
  • DB など重要サービスは 5 秒
  • 軽量サービスなら 15〜30 秒でも可

timeout:1 回のタイムアウト

1 回のチェックがハングした場合、Docker が待つ時間。短すぎると誤検知、長すぎると障害検知が遅れます。5〜10 秒が安全な範囲です。

retries:失敗リトライ回数

何回連続で失敗したら unhealthy とマークするか。

これはデバウンス機構です。ネットワークの一時的な揺れや DB の短時間過負荷で 1 回失敗することはあります。retries でシステムをより堅牢にします。

3〜5 回が妥当。retries=1 は敏感すぎ、retries=10 は鈍すぎます。

start_period:起動猶予期間(最も見落とされがち)

最も見落とされ、最もハマりやすいパラメータです。

start_period 内の失敗は retries にカウントされません。つまりサービスに「起動バッファ」を与えます。

なぜ重要か。DB の起動には時間がかかります。PostgreSQL はデータディレクトリを初期化し、MySQL はテーブルインデックスを読み込みます。start_period がなければ、2 秒目から healthcheck が失敗カウントを始め、retries が尽きる前に unhealthy とマークされる可能性があります。

推奨値

  • PostgreSQL / MySQL:30〜60 秒
  • 軽量サービス(Redis):15〜30 秒
  • 大量の init スクリプトがある場合:120 秒まで

私は通常 60 秒に設定します。誤検知より、少し長く待つ方が安全です。

よくあるエラーと回避ガイド

エラー 1:環境変数の参照が不正

# ❌ 誤り:Compose が起動前に展開し、コンテナはホスト側の変数値を受け取る
test: ["CMD", "mysqladmin", "ping", "-p$MYSQL_ROOT_PASSWORD"]

# ✅ 正しい:$$ でエスケープし、コンテナ内の shell に解析させる
test: ["CMD-SHELL", "mysqladmin ping -p$$MYSQL_ROOT_PASSWORD"]

エラー 2:start_period が短すぎる

# ❌ DB の初期化が終わる前に失敗カウントが始まり、すぐ unhealthy に
healthcheck:
  test: ["CMD", "pg_isready"]
  interval: 5s
  retries: 3
  start_period: 10s  # 短すぎ!

# ✅ 十分な起動時間を確保
healthcheck:
  start_period: 60s  # 安心

エラー 3:チェックツールが存在しない

このエラーは特に厄介です。Docker は静かに失敗するだけだからです。

# ❌ イメージに curl がなければ healthcheck は常に失敗
test: ["CMD", "curl", "-f", "http://localhost/health"]

# ✅ ツールを確保するか、イメージ標準のツールを使う
test: ["CMD", "wget", "--spider", "http://localhost/health"]  # Alpine イメージは wget 標準

healthcheck が設定できたら、具体的な DB の設定を見ていきましょう。

PostgreSQL ヘルスチェック実践設定

PostgreSQL 公式イメージには便利なツール pg_isready が同梱されています。

PostgreSQL が ready かどうかを確認する専用ツールで、自分で SQL クエリを書くより確実です。

基本設定(推奨)

version: '3.8'

services:
  web:
    image: node:20-alpine
    depends_on:
      db:
        condition: service_healthy  # 重要:ヘルスチェック通過を待つ
        restart: true  # DB 再起動時にアプリも再起動
    environment:
      DATABASE_URL: postgresql://postgres:password@db:5432/myapp
    command: npm start

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 60s
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

pg_isready コマンドの解説

pg_isready -U postgres -d myapp
  • -U:ユーザー名。実在するユーザーである必要がある
  • -d:データベース名(省略可だが指定推奨)

なぜ -U を付けるか。指定しないと pg_isready は現在のシステムユーザーで接続を試み、ログに warning が大量に出ます。機能には影響しませんが、見ていて煩わしいです。

発展設定:実際のクエリを追加

pg_isready はポート到達性だけを確認し、DB が本当にクエリを実行できるかまでは保証しません。より厳密なチェックが必要なら:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres && psql -U postgres -d myapp -c 'SELECT 1'"]
  interval: 10s
  timeout: 10s  # 追加クエリがあるので timeout を延長
  retries: 3
  start_period: 60s

SELECT 1 は最もシンプルなクエリです。成功すれば DB は起動しただけでなく、SQL を正常に処理できる状態です。

ただし大半のシーンでは、基本の pg_isready で十分です。

実際の起動効果

設定後、起動してみましょう。

$ docker-compose up

Creating network "myapp_default" ... done
Creating myapp_db_1 ... done
Waiting for myapp_db_1 to be healthy... この行に注目
Creating myapp_web_1 ... done

db_1   | PostgreSQL init process complete; ready for start up.
db_1   | database system is ready to accept connections
web_1  | Server listening on port 3000 DB ready 後にアプリ起動

明確な一時停止が見えます——Docker が db コンテナが healthy になるのを待っています。この過程は 30〜60 秒かかることもありますが、起動失敗ゼロの代償です。

トラブルシューティング:コンテナがずっと unhealthy

DB コンテナが unhealthy のままなら、ヘルスチェックログを確認します。

# コンテナのヘルス状態を確認
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq

{
  "Status": "unhealthy",
  "FailingStreak": 5,
  "Log": [
    {
      "Start": "2024-12-17T03:15:30Z",
      "End": "2024-12-17T03:15:30Z",
      "ExitCode": 1,
      "Output": "pg_isready: could not connect to server: Connection refused"
    }
  ]
}

よくある原因:

  1. start_period が短すぎる:DB がまだ初期化中に失敗カウントが始まる
  2. ユーザー名や DB 名が誤り:pg_isready が接続できない
  3. PostgreSQL 起動失敗:コンテナログ docker logs myapp_db_1 を確認

MySQL ヘルスチェック実践設定

MySQL のヘルスチェックには mysqladmin ping を使います。MySQL 標準の管理ツールです。

基本設定(推奨)

version: '3.8'

services:
  web:
    image: node:20-alpine
    depends_on:
      db:
        condition: service_healthy
        restart: true
    environment:
      DATABASE_URL: mysql://root:password@db:3306/myapp
    command: npm start

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: myapp
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 60s
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

mysqladmin ping コマンドの解説

mysqladmin ping -h localhost -u root -ppassword
  • -h:ホストアドレス(コンテナ内は localhost)
  • -u:ユーザー名
  • -p:パスワード(-p とパスワードの間にスペースなし)

MySQL が正常なら、このコマンドは次を返します。

mysqld is alive

終了コード 0 で、ヘルスチェック通過です。

パスワードの正しい扱い

方式 1:パスワードを直接記述(開発環境向け)

healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]

シンプルですが、設定ファイルにパスワードがハードコードされます。

方式 2:環境変数を使う(推奨)

db:
  environment:
    MYSQL_ROOT_PASSWORD: password
  healthcheck:
    test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
    # 注意:$ ではなく $$ を使う

ここに落とし穴があります。$ ではなく $$ を使う必要があります。

なぜか。Docker Compose は起動前に環境変数を展開します。$MYSQL_ROOT_PASSWORD を使うと、Compose はホスト側でこの変数を探します。$$ は「Compose は触らないで、コンテナ内の shell に解析させる」という意味です。

方式 3:パスワードなしチェック(最もシンプルだが議論の余地あり)

healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]

一部の MySQL 設定ではローカル接続をパスワードなしで許可します。この場合が最もシンプルですが、本番環境では非推奨です。

MySQL 8.0 の注意点

MySQL 8.0 はデフォルトで caching_sha2_password 認証プラグインを使い、古いクライアントが接続できないことがあります。アプリが認証エラーを出すなら、旧認証方式を強制できます。

db:
  image: mysql:8.0
  command: --default-authentication-plugin=mysql_native_password
  environment:
    MYSQL_ROOT_PASSWORD: password
    MYSQL_DATABASE: myapp
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
    interval: 10s
    timeout: 5s
    retries: 3
    start_period: 60s

よくある問題

問題 1:Access denied for user ‘root’@‘localhost’

パスワードが誤っているか、環境変数が効いていません。確認項目:

  1. MYSQL_ROOT_PASSWORD のスペル
  2. healthcheck 内のパスワードが一致しているか
  3. $$ エスケープを使っているか

問題 2:コンテナ起動が遅く、ずっと starting 状態

MySQL はデータディレクトリの初期化に時間がかかります。特に初回起動時。start_period を十分に(通常 60 秒)。大量データの init スクリプトがあるなら 120 秒以上。

問題 3:ヘルスチェックは通るが、アプリが DB に接続できない

ネットワークまたはアプリ設定の問題の可能性。確認項目:

  1. アプリの接続文字列(ホスト名は localhost ではなく db
  2. Docker ネットワーク設定
  3. docker network inspect でコンテナが同一ネットワークか

その他の DB とサービスのヘルスチェック

PostgreSQL と MySQL を押さえれば、他のサービスも応用できます。クイックリファレンスを載せます。

Redis

redis:
  image: redis:7-alpine
  healthcheck:
    test: ["CMD", "redis-cli", "ping"]
    interval: 10s
    timeout: 3s
    retries: 3
    start_period: 15s

redis-cli pingPONG を返し、終了コード 0。Redis は起動が速いので start_period は短めで可。

MongoDB

mongo:
  image: mongo:7
  healthcheck:
    test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
    interval: 10s
    timeout: 5s
    retries: 3
    start_period: 30s

注意:MongoDB 6.0 以降は旧 mongo コマンドの代わりに mongosh を使います。旧バージョンなら:

test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]

RabbitMQ

rabbitmq:
  image: rabbitmq:3-management-alpine
  healthcheck:
    test: ["CMD", "rabbitmq-diagnostics", "ping"]
    interval: 10s
    timeout: 5s
    retries: 3
    start_period: 40s

RabbitMQ は起動が比較的遅いので、start_period は 40 秒以上を推奨。

汎用 HTTP サービス

サービスが HTTP ヘルスチェックエンドポイント(/health/ping など)を提供するなら、wget や curl が使えます。

api:
  image: myapp:latest
  healthcheck:
    test: ["CMD", "wget", "--spider", "--quiet", "http://localhost:8080/health"]
    # curl を使う場合(イメージに curl があるなら)
    # test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    interval: 10s
    timeout: 3s
    retries: 3
    start_period: 20s

--spider はダウンロードせずチェックのみ、--quiet はログ出力を抑制します。

注意:イメージに wget または curl があることを確認。Alpine イメージは wget 標準、Debian/Ubuntu イメージは curl 標準です。

専用ツールがないサービス

ヘルスチェックツールがない場合、netcat(nc)でポートを確認できます。

service:
  image: some-service:latest
  healthcheck:
    test: ["CMD-SHELL", "nc -z localhost 9000 || exit 1"]
    interval: 10s
    timeout: 3s
    retries: 3
    start_period: 30s

nc -z はポートが開いているかだけを確認し、実際の接続は確立しません。ポート確認のみで、サービスが本当に ready かは保証できない——前述の方法より粗い手段です。

wait-for-it スクリプト:今も必要?

Docker の起動順序を調べると、wait-for-it や wait-for スクリプトを推す記事が多く見つかります。

これらはアプリコンテナの entrypoint に待機ロジックを追加し、依存サービスの TCP ポート到達性を確認する方式です。

従来の wait-for-it 方式

web:
  image: node:20-alpine
  depends_on:
    - db  # 通常の depends_on。ヘルス状態は確認しない
  volumes:
    - ./wait-for-it.sh:/wait-for-it.sh  # スクリプトをマウント
  command: ["/wait-for-it.sh", "db:5432", "--", "npm", "start"]

wait-for-it.sh は db:5432 ポートが接続可能になるまでループし、npm start を実行します。

なぜ非推奨になったか

2024 年のベストプラクティス:ネイティブ healthcheck が使えるならスクリプトは使わない

理由:

  1. 設定が明確:ヘルスチェックロジックが DB サービス側にあり、依存関係が一目瞭然
  2. 追加ファイル不要:スクリプトのメンテや volume マウントが不要
  3. 機能が強い:healthcheck は実際のサービス準備完了度を確認できる。TCP ポート確認は粗い
  4. 再利用性が高い:healthcheck を 1 回設定すれば、依存するすべてのサービスが恩恵を受ける

コミュニティには “Forget wait-for-it, use docker-compose healthcheck and depends_on instead” という人気記事もあります。

まだ wait-for-it が必要なケース

2 つの特殊ケースがあります。

ケース 1:イメージや compose 設定を変更できない

サードパーティイメージを使い、healthcheck がなく、追加権限もない場合。アプリ側に wait-for-it を足すのが唯一の選択肢です。

ケース 2:複数サービスを待つ必要がある

./wait-for-it.sh db:5432 redis:6379 rabbitmq:5672 -- npm start

depends_on でも複数設定できますが、wait-for-it の方が簡潔に書けます。ただしこのシーンは稀です。

その他の代替ツール

wait-for-it 以外にも類似ツールがあります。

  • dockerize:Go 製。機能が豊富、環境変数テンプレート対応
  • wait-for:wait-for-it の簡易版。pure shell 実装
  • docker-compose-wait:Python 製。HTTP チェック対応

ただし正直、2024 年なら healthcheck で十分。これらに手を出す必要はほぼありません。

トラブルシューティングリストとベストプラクティス

設定しても動かない?このリストに沿えば、9 割の問題は解決します。

クイック診断コマンド

# 1. 全コンテナの状態を確認
$ docker-compose ps

NAME       COMMAND    SERVICE   STATUS              PORTS
myapp_db   postgres   db        healthy             5432/tcp
myapp_web  npm start  web       running             0.0.0.0:3000->3000/tcp

# 2. ヘルスチェック詳細を確認
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq

# 3. コンテナログを確認
$ docker-compose logs db
$ docker-compose logs web

# 4. ログをリアルタイム追跡
$ docker-compose logs -f --tail=100

よくある問題の判断フロー

問題:コンテナがずっと starting で healthy にならない

  1. start_period が短すぎないか → 60s に変更してみる
  2. ヘルスチェックコマンドが正しいか → docker inspect で実際のコマンドを確認
  3. コンテナに入って手動実行 → docker exec -it myapp_db_1 pg_isready -U postgres

問題:unhealthy になったり healthy に戻ったりを繰り返す

  1. interval が短すぎてリソース不足 → 10s または 15s に変更
  2. retries が少なく偶発障害で反応 → 5 に変更
  3. DB 自体に性能問題 → DB ログを確認

問題:ヘルスチェックは通るが、アプリが DB に接続できない

  1. アプリの接続文字列(ホスト名はサービス名 db、localhost ではない)
  2. ポートマッピング(コンテナ間通信は内部ポート 5432、マッピング後のポートではない)
  3. ネットワーク設定(全サービスが同一 network か)

本番環境のベストプラクティス

1. 推奨パラメータ値(保守的設定)

healthcheck:
  interval: 10s          # 応答速度とリソース消費のバランス
  timeout: 5s            # コマンド実行に十分な時間
  retries: 5             # 偶発障害を許容
  start_period: 60s      # DB に十分な起動時間

このパラメータセットは大半のシーンで安定します。大量の init スクリプトがあるなら start_period を 120s に。

2. restart 戦略を使う

web:
  depends_on:
    db:
      condition: service_healthy
      restart: true  # DB 再起動時にアプリも再起動
  restart: unless-stopped  # コンテナ終了後に自動再起動

restart: true で DB アップグレードや再起動時に、依存サービスも再起動して再接続します。

3. リソース制限

ヘルスチェックもリソースを消費しますが、ごくわずかです。システムリソースが逼迫している場合:

healthcheck:
  interval: 30s  # チェック間隔を延長
  timeout: 3s    # タイムアウトを短縮

ただし正直、数百コンテナを動かさない限り healthcheck のオーバーヘッドは無視できます。

4. ヘルス状態を監視

本番環境では監視ツールでヘルスチェック状態を追跡するのがおすすめです。Docker のヘルスチェックイベントは Prometheus や Grafana などで収集できます。

# docker-compose.yml
services:
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 60s
    labels:
      - "prometheus.io/scrape=true"  # Prometheus にヘルス状態を収集させる

5. マルチ環境設定

開発環境と本番環境で設定を分けられます。

# docker-compose.yml(開発)
db:
  healthcheck:
    start_period: 30s  # 開発環境はデータが少なく起動が速い

# docker-compose.prod.yml(本番)
db:
  healthcheck:
    start_period: 120s  # 本番環境はデータが多く起動が遅い
    interval: 5s        # より頻繁にチェック

使用時は設定ファイルを指定:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

デバッグのコツ

コツ 1:ヘルスチェックコマンドを手動テスト

コンテナに入り、ヘルスチェックコマンドを手動実行して原因を特定:

$ docker exec -it myapp_db_1 sh
/# pg_isready -U postgres -d myapp
/var/run/postgresql:5432 - accepting connections
/# echo $?
0 0 が返れば成功

コツ 2:ヘルスチェックを一時無効化

デバッグ時は healthcheck をコメントアウトし、通常の depends_on でヘルスチェック自体の問題を除外:

web:
  depends_on:
    - db  # 一時的にシンプルモード
    # db:
    #   condition: service_healthy

アプリが DB に正常接続できることを確認してから、healthcheck を戻します。

コツ 3:Docker イベントログを確認

Docker はヘルスチェック状態変化を含むすべてのコンテナイベントを記録します。

$ docker events --filter 'event=health_status'

2024-12-17T03:15:30.123456789Z container health_status: healthy (name=myapp_db_1)
2024-12-17T03:16:45.987654321Z container health_status: unhealthy (name=myapp_db_1)

コンテナがいつ unhealthy になったかがわかり、ログのタイムスタンプと照合して問題を特定できます。

まとめ

記事冒頭の問題に戻りましょう。depends_on を設定してもなぜ動かないのか。

答えは 3 文字:不十分

depends_on はデフォルトではコンテナ起動だけを管理し、サービス準備完了までは保証しません。DB コンテナが running でも接続を受け付けられない——このギャップが問題の根源です。

解決策もシンプルです。

  1. DB に healthcheck を設定し、pg_isready や mysqladmin ping で実際の準備完了状態を確認
  2. service_healthy condition でアプリにヘルスチェック通過を待たせる
  3. 適切な start_period(60 秒以上)で DB に十分な初期化時間を確保

この 3 点で、コンテナ起動失敗の問題はほぼ解消します。

すぐできるアクション:

  • 今すぐ:記事の PostgreSQL または MySQL 設定をプロジェクトにコピーし、環境変数を調整
  • 今夜:チームプロジェクトの depends_on をすべて service_healthy にアップグレード
  • 来週のチーム会:同僚と共有し、チームの設定標準を統一

記事でカバーしていない問題があれば、コメントで教えてください。Docker Compose の落とし穴はまだまだあります——一緒に埋めていきましょう。

最後に、この記事で長らく悩んでいた起動問題が解決したなら、いいねを押して知らせてください。「やっと動いた」という瞬間が、記事を書く原動力です。

Docker Compose ヘルスチェック設定の完全フロー

DB 未準備によるアプリ起動失敗を解決。PostgreSQL と MySQL の完全な設定テンプレート付き

⏱️ 目安時間: 30 分

  1. 1

    ステップ1: 問題の根源を理解する:depends_on の限界

    問題の根源:
    • Docker の depends_on はコンテナ起動順序だけを管理し、サービスが本当に準備完了したかは確認しない
    • Compose はコンテナが走り始めれば OK とみなし、サービスが使えるかまでは待たない
    • Docker 公式ドキュメント:Compose does not wait until a container is "ready", only until it's running
    • つまり Compose はコンテナが走り始めれば OK で、サービスが使えるかまでは待たない

    コンテナ起動とサービス準備完了のタイムラグ:
    • 0 秒:Docker がコンテナを起動、postgres プロセス開始(depends_on はここで GO サイン)
    • 2 秒:データディレクトリ初期化
    • 5 秒:設定ファイル読み込み
    • 8 秒:init スクリプト実行(あれば)
    • 12 秒:ようやく ready、接続受付開始

    この 12 秒のギャップの間に Web アプリが DB 接続を試みると、Connection refused になる。
  2. 2

    ステップ2: depends_on の 3 つの condition 設定

    depends_on の 3 つの condition 設定:

    1. service_started(デフォルト)
    • コンテナ起動のみ待つ。サービス準備完了は待たない
    • 非推奨

    2. service_healthy(推奨)
    • コンテナ起動かつヘルスチェック通過を待つ
    • サービスが本当に使えることを保証

    3. service_completed_successfully
    • コンテナが正常終了するまで待つ
    • ワンショットタスク向け

    推奨設定:
    • docker-compose.yml で depends_on: db: condition: service_healthy
    • DB が本当に準備完了してからアプリを起動
  3. 3

    ステップ3: PostgreSQL と MySQL の正しい healthcheck の書き方

    PostgreSQL ヘルスチェック:
    • pg_isready コマンドで DB 準備完了を確認
    • 設定例:
    healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]
    interval: 5s
    timeout: 5s
    retries: 5
    start_period: 60s(初期化用の猶予時間)

    MySQL ヘルスチェック:
    • mysqladmin ping コマンドで DB 準備完了を確認
    • 設定例:
    healthcheck:
    test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
    interval: 5s
    timeout: 5s
    retries: 5
    start_period: 60s

    完全な設定例:
    • docker-compose.yml で healthcheck を定義(コマンド、間隔、タイムアウト、リトライ回数)
    • depends_on で service_healthy 条件を使用
    • DB が本当に準備完了してからアプリを起動
  4. 4

    ステップ4: トラブルシューティングとベストプラクティス

    トラブルシューティングリスト:
    1. ヘルスチェックコマンドの確認
    • コンテナ内で手動実行し、終了コード 0 か確認
    • 例:docker exec -it db_container pg_isready -U postgres

    2. ヘルスチェックの一時無効化
    • デバッグ時は healthcheck をコメントアウト
    • 通常の depends_on で、ヘルスチェック自体の問題か切り分け

    3. Docker イベントログの確認
    • docker events でコンテナ起動とヘルスチェックイベントを確認

    ベストプラクティス:
    1. DB に healthcheck を設定し、pg_isready や mysqladmin ping で実際の準備完了状態を確認
    2. service_healthy condition でアプリにヘルスチェック通過を待たせる
    3. 適切な start_period(60 秒以上)で初期化時間を確保

    これでコンテナ起動失敗の問題はほぼ解決する。

    wait-for-it スクリプトからの卒業:
    • depends_on + healthcheck の組み合わせを使用
    • 余計なスクリプト不要、設定シンプル、高信頼性
    • すべての Docker Compose プロジェクトに適用可能

FAQ

なぜ depends_on だけでは不十分?コンテナ起動とサービス準備完了の違いは?
問題の根源:Docker の depends_on はコンテナ起動順序だけを管理し、サービスが本当に準備完了したかは確認しません。Compose はコンテナが走り始めれば OK とみなし、サービスが使えるかまでは待ちません。

Docker 公式ドキュメントに重要な一文があります:Compose does not wait until a container is "ready", only until it's running。つまり Compose はコンテナが走り始めれば OK で、サービスが使えるかまでは待ちません。

コンテナ起動とサービス準備完了のタイムラグ:PostgreSQL コンテナの起動過程を想像してください:
• 0 秒:Docker がコンテナ起動、postgres プロセス開始(depends_on はここで GO サイン)
• 2 秒:データディレクトリ初期化
• 5 秒:設定ファイル読み込み
• 8 秒:init スクリプト実行(あれば)
• 12 秒:ようやく ready、接続受付開始

この 12 秒のギャップの間に Web アプリが DB 接続を試みると、Connection refused になります。
depends_on には 3 つの condition 設定がある?
depends_on の 3 つの condition 設定:

1) service_started(デフォルト):
• コンテナ起動のみ待つ。サービス準備完了は待たない
• 非推奨

2) service_healthy(推奨):
• コンテナ起動かつヘルスチェック通過を待つ
• サービスが本当に使えることを保証
• docker-compose.yml で depends_on: db: condition: service_healthy

3) service_completed_successfully:
• コンテナが正常終了するまで待つ
• ワンショットタスク向け

推奨:service_healthy で DB が本当に準備完了してからアプリを起動。
PostgreSQL と MySQL のヘルスチェックはどう設定する?
PostgreSQL ヘルスチェック:
• pg_isready コマンドで DB 準備完了を確認
• 設定例:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s(初期化用の猶予時間)

MySQL ヘルスチェック:
• mysqladmin ping コマンドで DB 準備完了を確認
• 設定例:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s

完全な設定例:docker-compose.yml で healthcheck を定義し、depends_on で service_healthy 条件を使い、DB が本当に準備完了してからアプリを起動。
ヘルスチェックの問題はどう切り分ける?
トラブルシューティングリスト:

1) ヘルスチェックコマンドの確認:
• コンテナ内で手動実行し、終了コード 0 か確認
• 例:docker exec -it db_container pg_isready -U postgres
• 0 が返れば成功

2) ヘルスチェックの一時無効化:
• デバッグ時は healthcheck をコメントアウト
• 通常の depends_on で、ヘルスチェック自体の問題か切り分け
• アプリが DB に接続できることを確認してから healthcheck を戻す

3) Docker イベントログの確認:
• docker events でコンテナ起動とヘルスチェックイベントを確認
ヘルスチェック設定のベストプラクティスは?
ベストプラクティス:
1) DB に healthcheck を設定し、pg_isready や mysqladmin ping で実際の準備完了状態を確認
2) service_healthy condition でアプリにヘルスチェック通過を待たせる
3) 適切な start_period(60 秒以上)で初期化時間を確保

この 3 点で、コンテナ起動失敗の問題はほぼ解決します。

wait-for-it スクリプトからの卒業:
• depends_on + healthcheck の組み合わせを使用
• 余計なスクリプト不要、設定シンプル、高信頼性
• すべての Docker Compose プロジェクトに適用可能

すぐできること:
• 記事の PostgreSQL または MySQL 設定をプロジェクトにコピーし、環境変数を調整
• チームプロジェクトの depends_on を service_healthy にアップグレード
• チームで設定標準を統一

6分で読めます · 公開日: 2025年12月17日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます