複数コンテナ構成でいちばん多い事故は「DBが起動する前にアプリが接続しにいって落ちる」です。depends_on を書いただけでは防げません——あれはコンテナの起動を待つだけで、中のサービスが使える状態かは見ていないからです。解決は healthcheck + condition: service_healthy の組み合わせです。
「ローカルでは動くのに、compose up すると初回だけ失敗する」——このパターンはほぼ起動順序の問題です。しかも2回目以降は成功するので原因が見えにくい。この記事では順序制御を確実にする書き方を扱います。
depends_on だけでは足りない理由
Compose v1の depends_on はコンテナが起動したことしか待ちません。PostgreSQLのコンテナが立ち上がっても、内部の初期化が終わってなければ接続は拒否されます。
結果として、APIコンテナが接続を試みた瞬間にDBがまだ準備できていない——というレース条件が発生します。マシンの速度や初回起動かどうかで結果が変わるので、再現性が低く厄介です。
Compose v2の改善点
v2で入った depends_on の condition キーが、この問題への公式な答えです。「起動を待つ」から「使える状態になるのを待つ」へ条件を指定できるようになりました。
healthcheckの書き方
依存される側(DBなど)に healthcheck を定義します。これが無いと service_healthy は使えません。
| 項目 | 意味 |
|---|---|
test |
健全性を判定するコマンド |
interval |
チェックの実行間隔 |
timeout |
1回の結果を待つ時間 |
retries |
unhealthy と判定するまでの連続失敗回数 |
start_period |
起動直後の猶予時間(この間の失敗は回数に数えない) |
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
api:
build: .
depends_on:
db:
condition: service_healthy
start_period を軽視しない
初期化に時間がかかるサービスでは、起動直後の失敗が retries を食い潰して unhealthy 扱いになります。start_period はその期間の失敗を回数にカウントしません。DBやElasticsearchのように立ち上がりが重いものでは、ここを適切に取るだけで安定します。
マイグレーションの完了を待つには
「DBが立ち上がる → マイグレーションを流す → アプリを起動する」という3段構えでは、真ん中のワンショットコンテナの終了を待つ必要があります。
これには condition: service_completed_successfully を使います。指定したサービスが正常終了するまで待ってから起動します。
services:
migrate:
build: .
command: ["./migrate.sh"]
depends_on:
db:
condition: service_healthy
api:
build: .
depends_on:
migrate:
condition: service_completed_successfully
| condition | 待つもの | 使いどころ |
|---|---|---|
service_started |
コンテナの起動 | 順序だけ揃えばいい場合 |
service_healthy |
healthcheckの成功 | DB・キャッシュなど接続先 |
service_completed_successfully |
正常終了 | マイグレーション・初期化ジョブ |
それでも落ちる場合は
健全性チェックが本当に「使える」を見ているか
プロセスが生きているかだけを見るヘルスチェックは、起動途中でも成功してしまいます。PostgreSQLなら pg_isready、HTTPサービスなら実際にエンドポイントを叩くなど、依存側が実際に使う経路で判定するようにしてください。
加えて、アプリケーション側にリトライを実装しておくのが本来の堅牢さです。本番ではDBの再起動やネットワークの瞬断が起こるので、起動順序の制御だけに頼る設計は脆いです。Composeの順序制御は「開発体験を良くするもの」、リトライは「本番で落ちないためのもの」と役割を分けて考えてください。
合わせて整えると効くこと
- イメージを軽くする——起動が速くなれば順序問題そのものが起きにくくなります(Dockerfile最適化のテクニック)。
- 環境ごとに設定を分ける——開発と本番でヘルスチェックの間隔を変えたいことがあります。
- ログを見る癖をつける——「なぜ unhealthy なのか」はヘルスチェックコマンドの出力に出ています。
VPS上での本番公開まで含めた構成はVPSにDocker+Nginx+SSLで公開する手順にまとめました。
まとめ
depends_on は起動を待つだけ。使える状態を待たせたいなら healthcheck + condition: service_healthy、ジョブの完了を待たせたいなら condition: service_completed_successfully。この2つで大半の順序問題は解決します。
そして start_period を忘れないこと。立ち上がりの重いサービスでは、ここが無いだけで不安定になります。最後に、本番を見据えるならアプリ側のリトライも併せて実装してください。

コメント