depends_on を書いたのに、アプリが「DBに接続できません」で落ちる。
原因は、depends_on がDBコンテナの起動しか待たないことです。
接続できる状態まで待たせるには、healthcheck と condition: service_healthy を組み合わせます。
ただ、よく見る書き方のままだと待ち時間が30秒に延びたり、初期化の途中で「準備完了」と誤判定されたりします。
2026年10月3日にDocker Engine 26.1.5 / Compose v2.32.1で計測した結果をもとに、正しい書き方をまとめました。
この記事でわかること
- healthcheck の書き方と5つのパラメータの既定値
- start_interval で起動待ちを31秒から2秒以内に縮める方法(実測)
- MySQL・PostgreSQLが初期化中に healthy になる落とし穴と対処
- curlがないイメージでの書き方と、unhealthy の調べ方(docker compose ps・docker inspect)
PARTS LIST /目次
Docker Compose healthcheckとは?depends_onだけでは足りない理由
Docker Composeの healthcheck とは、コンテナの中でコマンドを定期的に実行し、その結果でサービスが正常に動いているかを判定する仕組みです。
判定結果は starting・healthy・unhealthy の3つの状態で表されます。
depends_on と組み合わせると「依存先が healthy になってから起動する」という起動順の制御ができます。
逆に言えば、Composeファイルにもイメージ(Dockerfile の HEALTHCHECK)にも healthcheck がなければ、Composeはサービスの中身が使える状態かどうかを知る手段がありません。
「起動した」と「接続できる」は別物
depends_on の短い書き方は、依存先のコンテナが起動した時点で次のサービスを起動します。
ところがDBは、コンテナが起動してからも初期化に数秒〜数十秒かかります。
MySQLなら、データファイルの作成、初期化用の一時サーバーの起動、/docker-entrypoint-initdb.d のSQL実行と続き、最後に本番のサーバーが立ち上がります。
私が計測した MySQL 8.4 では、コンテナの起動から本番サーバーが接続を受け付けるまで32秒かかりました(初期化SQLに20秒かかる設定)。
アプリがこの32秒の間に接続しにいくと、当然エラーになります。
healthcheckは終了コード0か1かで判定する
healthcheck の判定は、指定したコマンドの終了コードだけで決まります。
Docker公式リファレンスによると、意味は次の3つです。
コンテナの状態は最初 starting で、チェックが1回成功すると healthy になります。
失敗が retries 回続くと unhealthy です。
コマンドの標準出力・標準エラーは先頭4,096バイトまで保存され、あとで docker inspect から読めます。
Composeのhealthcheckで状態が移り変わる流れは次のとおりです。
startinghealthystartingstartingのままstartingunhealthyhealthyunhealthyunhealthyhealthystarting から始まる。判定はコマンドの終了コード(0 が成功、1 が失敗)だけで決まる。healthcheckの書き方と5つのパラメータ
healthcheck は、サービス定義の下に test と4〜5個の時間・回数のパラメータを書くだけです。
迷うのは test の書き方と、各パラメータに何秒を入れるかの2点でしょう。
先に、この記事でおすすめする形を載せておきます。
既定値のままにすると起動待ちが無駄に長くなるので、start_period と start_interval は書いておいたほうが得です。
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD: example
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER:-postgres}"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
start_interval: 1s
testの書き方|CMDとCMD-SHELLの違い
test は配列か文字列で書きます。
配列の1つ目の要素で、コマンドの実行方法が変わります。
CMD-SHELL の中で環境変数を使うときは、$ を $$ と2つ重ねます。
Composeはファイルを読み込む時点で ${...} を自分で展開してしまうため、$$ にしてコンテナ内のシェルまで $ を届ける必要があります。
$ 1つで書いて、ホスト側に同じ名前の環境変数がないと、空文字に置き換わってチェックが失敗し続けます。
interval・timeout・retriesの既定値と決め方
5つのパラメータには、Docker公式リファレンスで既定値が決まっています。
timeout の既定値30秒は長すぎます。
DBへのpingは通常1秒もかからないので、5秒もあれば足ります。
timeout を超えたチェックは SIGKILL で止められ、失敗として数えられます。
interval は「起動後に壊れたことを何秒で検知したいか」で決めます。
開発環境なら30秒のままでも困りません。
start_intervalで起動待ちを31秒→2秒に短縮する
既定値のままだと、最初のチェックはコンテナ起動の30秒後まで実行されません。公式リファレンスに「最初のチェックは起動から interval 秒後に実行される」と書かれているとおりで、DBの準備が1秒で終わっても、healthy になるのは30秒後です。
PostgreSQL 17で実際に測った結果が次の表です。
start_period の間は、チェックが interval ではなく start_interval の間隔で実行されます。
猶予期間中の失敗は retries に数えられないので、1秒間隔で何回失敗しても unhealthy にはなりません。
start_interval は Docker Engine 25.0 以降、Compose 2.20.2 以降で使えます。
古い環境で書くとエラーになるので、docker version で確認してから使ってください。
DockerfileのHEALTHCHECKとの関係|Composeは項目ごとに上書きする
イメージの Dockerfile に HEALTHCHECK が書かれている場合、Compose の healthcheck はそれを丸ごと置き換えるのではなく、書いた項目だけを上書きします。
書かなかった項目は、Dockerfile 側の値がそのまま残ります。
HEALTHCHECK --interval=7s --timeout=3s を書いたイメージで、Compose 側の書き方を変えて確かめた結果が次の表です(Docker Engine 26.1.5 / Compose v2.32.1)。
つまり、イメージ付属のチェックコマンドはそのまま使い、間隔だけ縮めたいときは次のように書けます。
services:
web:
build: . # DockerfileにHEALTHCHECKがあるイメージ
healthcheck:
interval: 2s # testを書かなければDockerfileのコマンドが使われる
どの設定が効いているか分からなくなったら、コンテナの設定を直接見ると確実です。
docker inspect --format '{{json .Config.Healthcheck}}' "$(docker compose ps -q web)"
timeout を書いていないのに短い値になっている、といった食い違いは、たいてい Dockerfile 側の設定が残っているのが原因です。
depends_onとservice_healthyで起動順を制御する
healthcheck を書いただけでは、起動順は変わりません。
依存する側のサービスに、depends_on の長い書き方で condition: service_healthy を書いて初めて「healthy になるまで待つ」動きになります。
DBとアプリの2サービス構成なら、次の形が基本です。
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD: example
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER:-postgres}"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
start_interval: 1s
app:
build: .
depends_on:
db:
condition: service_healthy
restart: true
conditionの3種類と使い分け
condition には3つの値があります。
DB → マイグレーション → アプリの順に起動したい場合を考えます。
マイグレーション用サービスには service_healthy、アプリには service_completed_successfully を書けば、この順に並びます。
依存先が unhealthy のまま終わると、Composeは依存する側を起動しません。
画面には dependency failed to start: container ○○ is unhealthy と表示されます。
restart: trueとdocker compose up --wait
restart: true を付けると、Composeの操作で依存先を再起動・更新したときに、依存する側も自動で再起動されます。
DBを作り直したあと、アプリが古い接続を握ったままになる事故を防げます。
ただし対象は docker compose restart などの操作で起きた再起動だけで、コンテナが勝手に落ちて再起動した場合は含まれません。
docker compose up --wait は、全サービスが running か healthy になるまでコマンドを返さないオプションです。
CIで「起動しきってからテストを流す」用途に向いています。
待ち時間の上限は --wait-timeout で秒数を指定できます。
DB・サービス別のhealthcheck設定例
DB向けの healthcheck は、各DBのイメージに最初から入っている確認用コマンドを使うのが定番です。
curlを入れる必要がなく、DBのプロトコルで本当に応答するかを確かめられます。
ただし、ホストを指定しない書き方だと初期化中に healthy と誤判定されます。
ここは見落としやすい落とし穴なので、計測結果とあわせて説明します。
PostgreSQLはpg_isreadyで判定する
PostgreSQLは pg_isready で判定します。
ポイントは -h 127.0.0.1 を付けて、TCPで接続させることです。
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER:-postgres}"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
start_interval: 1s
公式イメージは初回起動時、初期化用の一時サーバーをUNIXソケットだけで立ち上げます。
ホストを省略した pg_isready はソケットにつなぐので、一時サーバーに応答して healthy になってしまいます。
初期化SQLに20秒かかる設定で比べた結果は次のとおりです。
Docker公式ドキュメントの例もホストを省略した形なので、初期化SQLを置いている人は特に注意が必要です。
MySQLはmysqladmin pingを127.0.0.1で使う
MySQLも同じ構造で、mysqladmin ping に -h localhost を付けるとソケット接続になります。
MySQLクライアントは localhost をソケット接続の意味で扱うため、TCPにしたいなら 127.0.0.1 と書きます。
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$${MYSQL_ROOT_PASSWORD}"]
interval: 30s
timeout: 5s
retries: 5
start_period: 60s
start_interval: 1s
MySQL 8.4で、初期化SQLに20秒かかる設定にして比べました。
ログを見ると、本番サーバーが接続を受け付け始めたのは32秒時点でした。
localhost 版は、その21秒前に「準備完了」を出していたわけです。
初期化SQLが一瞬で終わる環境では差が見えないため、テーブル作成やデータ投入を足した日に突然エラーが出始める、という形で表面化します。
なお、コマンドラインにパスワードを書くと警告が出ます。
開発環境なら実害はありませんが、本番では MariaDB 公式イメージに入っている healthcheck.sh のような専用スクリプトを検討してください。
RedisとSpring Bootアプリの設定例
Redisは redis-cli ping が PONG を返すかで判定します。
grep PONG を挟むと、PONG 以外の応答が返ったときも確実に失敗として扱えます。
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
start_interval: 1s
アプリ側にも healthcheck を書いておくと、docker compose up --wait やフロントのコンテナの起動待ちに使えます。
Spring Bootなら、spring-boot-starter-actuator を入れて /actuator/health を叩くのが手軽です。
DBに接続できないときは503が返るので、curl -f が失敗を返してくれます。
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
start_interval: 2s
eclipse-temurin:21-jre にはcurlが入っているので、ベースイメージがこれならそのまま書けます。
curlがないイメージでhealthcheckを書く方法
Webサーバーの healthcheck は curl -f http://localhost/ で書かれている例が多いものの、curlが入っていないイメージも珍しくありません。
2026年10月3日時点で、よく使うイメージにcurl・wget・ncが入っているかを確かめました。
curlがないのに ["CMD", "curl", ...] と書くと、チェックのたびに「executable file not found」で失敗し、永遠に healthy になりません。
wgetやncで代用する
Alpine系のイメージなら、BusyBox の wget か nc で代用できます。
HTTPで応答を確かめたいなら wget、ポートが開いているかだけでよければ nc です。
# HTTPの応答を確かめる(wget)
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
# ポートが開いているかだけ確かめる(nc)
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "8080"]
nc はポートが開いた瞬間に成功するので、アプリの初期化が終わったかまでは分かりません。
アプリにヘルスチェック用のURLがあるなら、wget で叩くほうが確実です。
シェルもないイメージ(distroless)の対処
distroless のようにシェルもcurlもないイメージでは、CMD-SHELL 自体が動きません。
対処は2つあります。
- アプリ自身が持っているヘルスチェック用のサブコマンドを
["CMD", ...]で直接呼ぶ - マルチステージビルドで、静的リンクの BusyBox だけをコピーして使う
2つ目は、次のように busybox:1.37-musl から1ファイルだけ持ってきます。
gcr.io/distroless/static-debian12 に入れて、wget が動くことを確認しました。
FROM busybox:1.37-musl AS bb
FROM gcr.io/distroless/static-debian12
COPY --from=bb /bin/busybox /busybox
healthcheck:
test: ["CMD", "/busybox", "wget", "-q", "--spider", "http://localhost:8080/health"]
足されるのは約1.3MBのBusyBox 1ファイルだけです。
イメージを小さく保つために distroless を選んでいる場合でも、healthcheck が書けない不便さと比べれば許容しやすいサイズでしょう。
healthcheckがunhealthyになる原因と調べ方
healthcheck が通らないとき、Composeの画面には「unhealthy」としか出ません。
原因を突き止めるには、チェックの実行結果そのものを読みにいく必要があります。
調べ方を知っていれば、大半は数分で片づきます。
docker compose psで今の状態を確認する
まずは、各サービスがいまどの状態にあるかを確認します。
docker compose ps --format 'table {{.Service}}\t{{.Status}}'
STATUS 列の末尾に、healthcheck の状態がカッコ書きで表示されます。
SERVICE STATUS
app Up 4 seconds (health: starting)
db Up 12 seconds (healthy)
redis Up 12 seconds (unhealthy)
(health: starting) は最初のチェックが通る前、(healthy) は正常、(unhealthy) は失敗が retries 回続いた状態です。
カッコ書きが出ないサービスは、healthcheck が設定されていないか、disable: true で無効になっています。
starting のまま進まない、unhealthy になった、という場合は次のヘルスログで中身を読みます。
docker inspectでヘルスログを読む
チェックの結果は、直近の数回分がコンテナに保存されています。
次のコマンドで、終了コードと出力を確認できます。
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q db)"
curlがないイメージで試すと、Output に次のメッセージが入っていました。
OCI runtime exec failed: exec failed: unable to start container process: exec: "curl": executable file not found in $PATH: unknown
チェックのコマンドを、コンテナの中で手で実行してみるのも有効です。
docker compose exec db pg_isready -h 127.0.0.1 -U postgres; echo "exit=$?" のように終了コードまで表示すると、どこで失敗しているかがすぐ分かります。
よくある原因4つと対処
unhealthy になる原因は、だいたい次の4つに収まります。
逆に「早く healthy になりすぎて、アプリが接続エラーになる」場合は、前の章のソケット接続の問題を疑ってください。
unhealthyでもコンテナは再起動されない
意外と知られていませんが、unhealthy になってもDockerはコンテナを再起動しません。
restart: always を付けたコンテナで healthcheck を失敗させ続けても、再起動回数は0のままでした。
healthcheck はあくまで状態を判定する仕組みで、復旧までは面倒を見てくれません。
自動で再起動させたいなら、アプリ側で異常を検知したらプロセスを終了させ、restart ポリシーに再起動させる設計が素直です。
Docker Compose healthcheckのよくある質問
Docker Compose の healthcheck について、検索でよく調べられている疑問をまとめました。
起動順の制御、パラメータの決め方、curlがないときの対処が質問の中心です。
詳しい手順や計測結果は、各見出しの本文もあわせて確認してください。
Q1. healthcheckとdepends_onの違いは?
A1. healthcheck はサービスが正常かを判定する仕組みで、depends_on は起動順を決める仕組みです。
depends_on に condition: service_healthy を書くと、healthcheck の判定結果を使って「healthy になるまで待つ」動きになります。
Q2. interval・timeoutは何秒にすればいい?
A2. 開発環境なら interval 30秒、timeout 5秒、retries 3回で足ります。
起動待ちを短くしたいなら、start_period を30〜60秒、start_interval を1〜2秒にしてください。
既定値の timeout 30秒は長すぎるので、短くしておくのがおすすめです。
Q3. unhealthyになったら自動で再起動される?
A3. されません。
Docker Composeで動かすコンテナは、unhealthy になっても restart ポリシーによる再起動の対象になりません。
再起動させたい場合は、アプリ側で異常時にプロセスを終了させる設計にします。
Q4. curlが入っていないイメージではどうする?
A4. Alpine系なら BusyBox の wget か nc で代用できます。
DBなら pg_isready や mysqladmin ping のように、イメージに付属するコマンドを使うのが確実です。
Q5. healthcheckを無効にするには?
A5. disable: true を書くか、test を ["NONE"] にします。
ベースイメージの Dockerfile に HEALTHCHECK が書かれていて、それを止めたいときに使います。
Q6. 「condition: service_healthyが使えない」と言われたら?
A6. docker-compose(v1)の 1.27.0 より前で version: '3' 系のファイル形式を指定していると、condition の書き方がエラーになります。3系のファイル形式から condition が一度削除されていたためで、1.27.0 以降は使えるようになっています。
現在の Docker Compose v2 では、トップレベルの version は不要で、condition もそのまま使えます。
Docker Compose healthcheckの要点まとめ
healthcheck は数行書くだけの設定ですが、既定値とホスト指定の2か所で結果が大きく変わります。
最後に、書くときに押さえる点を整理します。
【要点まとめ】
- 起動順を待たせるには depends_on の
condition: service_healthyが必要 - 既定値のままだと初回チェックは30秒後。start_interval で1〜2秒に縮む
- DBのチェックは
-h 127.0.0.1でTCP接続にする(ソケットだと初期化中に healthy になる) - 環境変数は
$$で書く。curlがなければ wget・nc・DB付属コマンドで代用する - unhealthy になっても再起動はされない。原因は docker inspect のヘルスログで読む
今回の計測でいちばん意外だった結果は、PostgreSQL の healthcheck が初期化の途中で healthy を返したことです。
Docker公式ドキュメントの例と同じく、ホストを省略した書き方でした。
初期化SQLを置いていないうちは表に出ません。
手元の compose.yaml が pg_isready や mysqladmin ping をホストなしで呼んでいないか、一度見直してみてください。
Composeファイル全体の書き方は「Docker Compose入門|yml書き方と使い方」で解説しています。
コンテナ同士の通信やポートの考え方は「Dockerネットワークとポート設定」が参考になります。
| REV. | DATE | 内容 |
|---|---|---|
| 3 | 2026-10-16 | 図解を描き直し(図面のデザインにそろえた) |
| 2 | 2026-10-04 | 記載内容の事実確認と修正 |
| 1 | 2026-10-03 | 初版 |

