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

Docker Composeサービス依存関係:ヘルスチェック設定でデータベース起動順序問題を解決する

「データベースに接続できません。Connection refused。」

Docker Composeを使ってアプリケーションを開発しているとき、このエラーに遭遇したことはありませんか?
「あれ?depends_onでデータベースを指定したはずなのに、なんで?」と思ったことでしょう。

そして多くの人が、wait-for-it.shのようなシェルスクリプトを探してきて、コンテナ起動時にデータベースのポートが開くのを待つようなハックをしてしまいます。

しかし、2025年の今、もっとエレガントでDockerネイティブな解決策があります。外部スクリプトはもう不要です。今日は、Docker Composeのhealthcheck機能を使って、この問題を根本から解決する方法を解説します。

なぜdepends_onだけでは不十分なのか?

ドキュメントをよく読むと、衝撃的な事実が書いてあります。

depends_on expresses start order, not readiness.

つまり、depends_onは「起動順序」を保証するだけで、「準備完了」までは待ってくれないのです。

よくある失敗シナリオ

  1. Dockerがデータベースコンテナ(PostgreSQLなど)を起動します。
  2. PostgreSQLのプロセスが立ち上がります。この時点でDockerは「起動完了」と判断します
  3. Dockerが依存するWebアプリコンテナを起動します。
  4. Webアプリが即座にデータベースに接続しようとします。
  5. しかし、PostgreSQLはまだ初期化処理(データディレクトリの作成、configの読み込みなど)を行っており、ポート35432はまだリクエストを受け付けていません
  6. Webアプリがクラッシュします。「Connection refused」。

プロセスが起動していることと、サービスがリクエストを受け付けられる状態であることは、全く別物なのです。

正解:Healthcheck + Condition

この問題を解決するには、Dockerに「コンテナが起動したか」ではなく、「サービスが健全(Healthy)か」を判断させる必要があります。

これには2つのステップが必要です:

  1. データベースコンテナにhealthcheck(健康診断)を定義する。
  2. Webアプリコンテナのdepends_onで、condition: service_healthyを指定する。

実践:PostgreSQLの場合

PostgreSQLにはpg_isreadyという便利なユーティリティが含まれています。これを使います。

version: '3.8'

services:
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    # ステップ1:ヘルスチェックを定義
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 5s       # 5秒ごとにチェック
      timeout: 5s        # タイムアウト5秒
      retries: 5         # 5回失敗したらUnhealthyとみなす
      start_period: 10s  # 起動直後の10秒間は失敗してもカウントしない
    ports:
      - "5432:5432"

  app:
    build: .
    # ステップ2:Healthyになるまで待つ
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://user:password@db:5432/mydb

これで、Webアプリはpg_isreadyコマンドが成功(exit code 0)を返すまで、つまりデータベースが本当に接続を受け付けられるようになるまで、起動を待機します。

実践:MySQLの場合

MySQLの場合はmysqladmin pingを使います。

version: '3.8'

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: mydb
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    # ステップ1:ヘルスチェックを定義
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 30s # SQLiteなどと違い、MySQLの初期化は重いので長めに
    ports:
      - "3306:3306"

  app:
    build: .
    # ステップ2:Healthyになるまで待つ
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: mysql://user:password@db:3306/mydb

注意点:docker-compose.yml内で環境変数を使用する場合、$$$とエスケープする必要があります(例:-p$$MYSQL_ROOT_PASSWORD)。

その他のサービス(Redis, Elasticsearchなど)

Redis:

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

Elasticsearch:

healthcheck:
  test: ["CMD-SHELL", "curl -s http://localhost:9200 >/dev/null || exit 1"]
  interval: 10s
  timeout: 10s
  retries: 5

基本的に、「そのサービスが応答するかどうか」を確認するコマンドがあれば何でもOKです。curl、wget、専用CLIツールなどが使えます。

start_periodの重要性

healthcheckの設定の中にstart_periodという項目がありましたね。これは非常に重要です。

データベースの初回起動時は、初期データの投入などが行われるため、通常より起動に時間がかかります。この期間中にヘルスチェックが失敗しても、それは「異常」ではなく「準備中」です。

start_periodを設定しないと、初期化中にretries回数を超えて失敗してしまい、Dockerが「このコンテナは壊れている」と判断して再起動を繰り返す(Restart Loop)可能性があります。

  • PostgreSQL: 10秒〜30秒あれば十分
  • MySQL/MariaDB: 初回は重いので30秒〜60秒推奨

結論

wait-for-it.shsleep 10のようなハックはもう卒業しましょう。Docker Compose標準の機能だけで、堅牢な依存関係管理が可能です。

  1. データストア側のコンテナにhealthcheckを書く。
  2. アプリ側のdepends_oncondition: service_healthyを書く。

たったこれだけで、“Connection refused”による起動失敗は過去のものになります。今日のコミットで、あなたのdocker-compose.ymlをアップデートしてみませんか?

Docker Compose健康チェック設定完全フロー

データベース未準備によるアプリケーション起動失敗問題を解決する、PostgreSQLとMySQLの完全な設定テンプレート

⏱️ Estimated time: 30 min

  1. 1

    Step1: 問題の核心を理解する:depends_onの限界

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

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

    この間の12秒のラグでWebアプリが接続しようとすると、Connection refusedになる。
  2. 2

    Step2: 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 と設定する。
    • これにより、データベースが本当に準備完了してからアプリが起動する。
  3. 3

    Step3: PostgreSQLとMySQLの正しいhealthcheckの書き方

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

    MySQLヘルスチェック:
    • mysqladmin pingコマンドを使用
    • 設定例:
    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条件を使用
    • これでデータベースが確実に準備完了してからアプリが起動する
  4. 4

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

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

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

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

    ベストプラクティス:
    1. データベースには必ず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はコンテナが起動すればすぐに次のサービスの起動を開始します。

Docker公式ドキュメントにも「Compose does not wait until a container is "ready", only until it's running」と明記されています。

例:PostgreSQLコンテナが起動してから実際に接続を受け付けられるようになるまで(初期化、設定読み込み等で)数秒~数十秒かかります。この間にアプリが接続しようとすると失敗します。
depends_onにはどのような設定がありますか?
3つのcondition設定があります:

1) service_started(デフォルト):
• コンテナ起動のみ待ちます。非推奨。

2) service_healthy(推奨):
• コンテナ起動かつヘルスチェック通過を待ちます。サービスが本当に使えることを保証します。
• 設定:depends_on: db: condition: service_healthy

3) service_completed_successfully:
• コンテナが正常終了するのを待ちます。ワンショットタスク向け。
PostgreSQLとMySQLのヘルスチェックはどう設定しますか?
PostgreSQL:
• pg_isreadyコマンドを使います。
• healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"]

MySQL:
• mysqladmin pingコマンドを使います。
• healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]

共通設定:
• interval: 5s, timeout: 5s, retries: 5
• start_period: 60s(初期化待ち時間を確保)
ヘルスチェックの問題はどう排查しますか?
1) コマンド確認:
• コンテナ内で手動実行し、正常終了(exit code 0)するか確認します。
• 例:docker exec -it db_container pg_isready -U postgres

2) 一時無効化:
• healthcheckをコメントアウトして、ヘルスチェック自体の問題か切り分けます。

3) ログ確認:
• docker eventsでイベントを確認します。
ベストプラクティスは何ですか?
1) データベースには必ずhealthcheck(pg_isready/mysqladmin ping)を設定する。
2) アプリ側でservice_healthy conditionを使って待機させる。
3) start_periodを適切(60秒以上)に設定して、初期化タイムアウトを防ぐ。

この3点で、wait-for-itなどの外部スクリプトを使わずに、Docker標準機能だけで完結するモダンな構成になります。

2 min read · 公開日: 2025年12月17日 · 更新日: 2026年1月22日

コメント

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

関連記事