Docker ComposeでAI生成ローカル環境の起動失敗を10分で直す — 4確認の診断順
AIに「APIとPostgreSQLをDocker Composeで動かして」と頼むと、数十秒でそれらしいYAMLが返ってきます。
ところが docker compose up を実行すると、DBは動いているように見えるのにAPIだけ落ちる。もう一度起動すると、なぜか今度は動く。AIへログを貼るたびに修正箇所が増えて、最後はYAMLを全部書き直している。
なんかこれ、実装時間は短くなったのに、原因を推測する時間が増えてしまってるんですよね。
先に結論です。
Docker Composeの起動失敗は、次の4つを順番に確認すると、推測修正をかなり減らせます。
-
docker compose configで「実際に適用される設定」を見る -
docker compose logsで「最初の失敗」を見る - healthcheckで「起動済み」と「準備完了」を分ける
- サービス名DNSで「どこへ接続しているか」を見る
この記事では、この順番を 「起動失敗の4確認」 と呼びます。設定、ログ、準備完了、名前解決の順で観測し、推測より先に故障した層を固定する診断手順です。
読了後のゴールは、Dockerの達人になることではありません。10分で最初に直す設定を1つ選べること。 まずはそこまでで十分です。
まず知っておきたい: runningとreadyは別です
Composeファイルに depends_on を書くと、依存するサービスの起動順を指定できます。
ただし、ここで見落としやすいことがあります。
コンテナがrunningになったことと、その中のアプリが要求を受けられるready状態になったことは別です。
たとえばPostgreSQLのコンテナプロセスが開始しても、初期化やリカバリーが終わるまではSQL接続を受けられないことがあります。Docker公式ドキュメントも、Composeは既定ではコンテナがrunningになるまで待つだけで、readyになるまでは待たないと説明しています。
短い形式の depends_on は、店員さんの出勤順を決めるようなものです。店員さんが店に着いたからといって、レジも仕込みも終わっているとは限らない。開店できる条件は、別に定義する必要があります。
この違いが分かると、「2回目だけ動く」という現象が少し読みやすくなります。1回目はAPIがDBの準備完了より先に接続し、2回目はDBがすでにreadyだった。そういう時間差の可能性を、証拠で確認できるからです。
10分の初回成功: 接続先の誤りを1つ見つける
まず、DBを本当に起動しなくても見つけられる誤りから始めます。
次の compose.yaml を見てください。説明用のダミー値なので、実システムの秘密情報は含めていません。
services:
api:
image: python:3.13-slim
command:
- python
- -c
- |
import os
print("database:", os.environ["DATABASE_URL"])
environment:
DATABASE_URL: postgresql://app:dummy@localhost:5432/app
depends_on:
- db
db:
image: postgres:18
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: dummy
POSTGRES_DB: app
ここで最初に実行するのは、upではありません。
docker compose config -q
docker compose config
docker compose config -q は設定を検証し、問題がなければ何も表示せず終了します。続く docker compose config は、複数のComposeファイルをマージし、環境変数を解決し、短縮記法を正規形へ展開した結果を表示します。
見る場所は api.environment.DATABASE_URL です。
postgresql://app:dummy@localhost:5432/app
この localhost が最初の修正候補です。
コンテナの中で localhost と書くと、そのコンテナ自身を指します。つまりAPIコンテナから localhost:5432 へ接続しても、探しに行くのはDBコンテナではなくAPIコンテナです。
Composeが作る既定ネットワークでは、同じネットワーク上のサービスを サービス名 で見つけられます。DBサービスの名前は db なので、コンテナ間の接続先は次のようにします。
postgresql://app:dummy@db:5432/app
まだ全部は直っていません。でも、10分で「接続先が間違っている」という観測可能な修正候補を1つ選べました。
これが最初の成功です。
確認1: configで「AIが書いたYAML」ではなく「適用される設定」を見る
AIへレビューを頼む前に、元のYAMLだけを見るのは少し危険です。
Composeは次のような処理をしてから設定をDocker Engineへ渡します。
-
-fで指定した複数ファイルのマージ -
.envや環境変数の補間 - 相対パスの解決
- 短縮記法の展開
人間が読んでいるファイルと、実際に適用される設定がずれることがあるんです。
なので、最初の証拠は正規化済みの設定にします。
# 検証だけ。CIにも置きやすい
docker compose config -q
# 適用されるサービス名を確認
docker compose config --services
# 正規化済み設定を確認
docker compose config
ここで見るのは、次の4点です。
- 想定したサービスが存在するか
- 接続先ホスト名が
localhostや固定IPになっていないか - コンテナ間通信にホスト側ポートを使っていないか
- 環境変数が空文字や想定外の値へ展開されていないか
たとえば ports: "8001:5432" の場合、8001 はホストから接続するためのHOST_PORTです。同じComposeネットワークにいるAPIからDBへ接続するときは、サービス名 db とコンテナ側の 5432 を使います。
ホストPC → localhost:8001
APIコンテナ → db:5432
この2つを混ぜると、YAMLの構文は正しいのに接続だけ失敗します。構文エラーと接続設計のエラーは別物。ここを分けるのが1つ目の確認です。
configをAIへ渡すときの安全ルール
docker compose config の出力には、環境変数展開後のパスワード、トークン、内部ホスト名などが含まれる場合があります。
外部AIへ貼る前に、次を削除またはダミー化します。
- パスワード、APIキー、トークン
- 個人情報を含むパスや値
- 非公開のホスト名、レジストリURL
- 組織固有の識別子
便利な証拠ほど、情報量が多い。だから 証拠を集めることと、外へ送ってよいことは別判断 にしておく方が安全です。
確認2: logsで「最後のエラー」より「最初の失敗」を見る
設定が正しそうなら、次はログです。
docker compose logs \
--tail 100 \
--timestamps \
api db
docker compose logs はサービスのログを表示します。--tail 100 で末尾100行、--timestamps で時刻を付けます。
見る順番はこうです。
- APIが最初に失敗した時刻
- その直前のDBログ
- エラーの種類
- APIが再試行したか、そのまま終了したか
よくあるログは、同じ「接続できない」でも意味が違います。
| ログの例 | 最初に疑う層 |
|---|---|
connection refused |
相手が未準備、ポート違い、待受なし |
could not translate host name |
サービス名、ネットワーク、DNS |
password authentication failed |
認証情報、DB初期化済みvolume |
database does not exist |
DB名、初期化処理 |
exited with code 1 |
その直前に出たアプリ側エラー |
ここで大事なのは、AIに「直して」と丸投げする前に 事実と仮説を分けること です。
connection refused は事実です。「depends_onが悪い」は仮説です。DBのhealth状態、接続先、待受ポートがまだ見えていないなら、修正より先に証拠を追加します。
最後に出た長いスタックトレースだけを見ると、最初の接続失敗が埋もれることがあります。障害調査では、派手な最後より、静かな最初。なんかこの順番の方が、AIにも人間にも説明しやすい気がします。
確認3: healthcheckで「準備完了」を機械判定する
接続先が正しく、ログに起動競合が見えたら、healthcheckを検討します。
PostgreSQLを使う最小構成は、こんな形です。
services:
api:
image: python:3.13-slim
command:
- python
- -c
- |
import os
print("database:", os.environ["DATABASE_URL"])
environment:
DATABASE_URL: postgresql://app:dummy@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:18
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: dummy
POSTGRES_DB: app
healthcheck:
test:
- CMD-SHELL
- pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
ポイントは2つです。
-
db.healthcheckがDBの準備完了を判定する -
api.depends_on.db.conditionがservice_healthyを待つ
$$ とドル記号を2つ書いているのは、Compose側ではなくコンテナ内のシェルで POSTGRES_USER と POSTGRES_DB を展開させるためです。
状態は次のコマンドで確認できます。
docker compose ps --all --format json
Docker公式によると、docker compose ps はComposeプロジェクトのコンテナ、現在状態、公開ポートを表示します。JSON形式では State、Health、ExitCode も扱えるので、目視だけでなく診断スクリプトにもつなげられます。
#!/usr/bin/env bash
set -eu
docker compose config -q
echo "== services =="
docker compose config --services
echo "== status =="
docker compose ps --all --format json
echo "== recent logs =="
docker compose logs --tail 50 --timestamps api db
このスクリプトは設定を変更しません。設定、状態、直近ログを集めるだけです。いきなり再ビルドやvolume削除をしないので、最初の切り分けとして使いやすいかと。
healthcheckでよくある新しい失敗
healthcheckを追加したのに、永遠に unhealthy になることがあります。
そのときは、次を確認します。
-
testに書いたコマンドが、そのイメージ内に存在するか - コマンドの終了コードが、成功時に0になるか
-
start_periodが初期化時間より短すぎないか - 認証や接続先がhealthcheck内でも正しいか
- 「プロセスがいる」ではなく、必要な機能が使えることを測っているか
たとえば小さなイメージには curl が入っていないことがあります。curlのhealthcheck例をAIが出したからといって、そのまま動くとは限りません。
healthcheckは、イメージの中で実行できるコマンドで定義する。 ここは人間が確認する場所です。
確認4: サービス名DNSで「どこへ接続しているか」を固定する
Composeは既定でアプリ用のネットワークを1つ作り、各サービスをそのネットワークへ参加させます。同じネットワーク上のコンテナは、サービス名を使って相手を見つけられます。
services:
api:
environment:
DATABASE_HOST: db
DATABASE_PORT: "5432"
db:
image: postgres:18
この場合、APIコンテナからの接続先は db:5432 です。
固定IPを設定する必要はありません。Docker公式も、コンテナを再作成するとIPは変わり得る一方、サービス名は維持されるため、IPではなく名前を参照するよう案内しています。
ここで、3つの場所を混ぜないようにします。
| 接続元 | 接続先の例 |
|---|---|
| ホストPC | localhost:8001 |
| 同じCompose内のAPI | db:5432 |
| APIコンテナ自身 | localhost:<API自身のポート> |
もしサービスが別々のComposeプロジェクトにいるなら、同じ既定ネットワークにはいません。その場合は共有する外部ネットワークなど、別の設計が必要です。
つまり「サービス名でつながる」は、同じネットワークに参加している という前提付きです。万能な魔法ではありません。
AIへ渡すプロンプト1: 事実・仮説・不足証拠を分ける
AIはログを読むのが速いです。ただ、証拠が足りないままでも、それらしい原因を1つ選べてしまいます。
そこで、修正案の前に分類を頼みます。
あなたはDocker Compose障害の調査補助者です。
以下の「正規化済みCompose設定」「psの状態」「時刻付きログ」を読み、
出力を次の3列に分けてください。
1. 観測できた事実
2. 原因の仮説
3. 仮説を判定するために不足している証拠
制約:
- まだ修正コードは出さない
- ログにない事実を補わない
- config → logs → health → service-name DNSの順で分類する
- 秘密値はすべてダミー化済みとして扱う
期待する最後の出力:
- 次に実行する読み取り専用コマンドを最大3つ
- 各コマンドで何が分かるか
このプロンプトの成果は「正解を当てること」ではありません。次に取る証拠を選べること です。
AIへ渡すプロンプト2: 最小差分だけを作る
原因候補が接続先と起動競合に絞れたら、初めて修正案を頼みます。
以下のCompose設定に対し、最小差分の修正案をunified diffで出してください。
確認済みの事実:
- APIコンテナ内のDATABASE_URLがlocalhost:5432を向いている
- DBサービス名はdb
- APIログはDB準備完了前にconnection refusedで終了している
修正範囲:
- DATABASE_URLの接続先
- DBのhealthcheck
- APIのdepends_on condition
禁止:
- unrelatedなimage変更
- volume削除
- パスワード変更
- ポート公開の追加
差分の後に、各変更がどの観測事実に対応するかを1行ずつ説明してください。
動作を保証せず、未確認事項を明記してください。
AIの変更範囲を先に狭めると、「ついでの改善」で新しい故障を増やしにくくなります。
AIへ渡すプロンプト3: 直った後をRunbookに変える
一度直っても、翌月また同じ調査をゼロから始めたら、時間は戻ってきません。
今回の障害調査を、チーム向けの10分Runbookへ変換してください。
必須構成:
1. 症状
2. 最初に実行する読み取り専用コマンド
3. 期待する正常結果
4. 異常結果ごとの次の分岐
5. 実行してはいけない破壊的操作
6. 外部AIへ渡す前に匿名化する項目
7. エスカレーション条件
診断順は次で固定:
config → logs → health → service-name DNS
今回固有の秘密値や内部名は一般化し、
コピペ用コマンドにはプレースホルダーを使ってください。
無料で使える汎用プロンプトだけでも、次回の調査時間は短くできます。そのうえでチーム固有の「準備完了条件」「許可されたコマンド」「秘密情報の境界」を足すと、自分たちのRunbookになります。
コピーできる文面より、判断基準が更新され続ける状態の方が、長く効く資産になるんやと思います。
人間が設計し、AIに任せること
責任分界を曖昧にすると、AIが設定ファイルの作者だけでなく、実行許可者にもなってしまいます。
おすすめの分け方は次です。
| 人間が担当 | AIに任せやすい |
|---|---|
| readyの意味を決める | config差分の要約 |
| 実行してよい環境と負荷を決める | ログの時系列整理 |
| 外部へ出せる情報を決める | 事実・仮説・不足証拠の分類 |
| 修正の採用とロールバックを決める | 最小差分の草案 |
| 本番要件を決める | Runbookの初稿 |
たとえば「DBのポートが開いた」をreadyとするか、「必要なテーブルへSELECTできた」をreadyとするかは、システム要件で変わります。AIは候補を出せますが、どこまでを正常と呼ぶかは人間の設計です。
逆に、正規化済み設定の比較や、100行のログから最初の失敗時刻を抜く作業はAIが得意です。
人間が全部読む必要はない。でも、人間が何を正常と呼ぶかまで手放さない。ここがちょうどええ境界かなと。
反証: healthcheckを足しても効かない条件
ここまで読むと、「全部の依存サービスにhealthcheckを書けば解決する」と見えるかもしれません。
でも、効かない条件があります。
- 起動後にDBや外部APIが一時的に落ちる
- アプリが接続切断後に名前解決と再接続をしない
- 障害原因がCPU、メモリ、ディスク不足にある
- 別Composeプロジェクトでネットワークを共有していない
- 認証情報やDB初期化済みvolumeが食い違っている
- healthcheck自身のコマンドが存在しない
healthcheckと service_healthy が主に助けるのは、初回起動時の準備完了待ち です。実行中の一時障害に耐えるには、アプリ側のタイムアウト、指数バックオフ、再接続、サーキットブレーカーなどが必要になる場合があります。
無料の4確認だけで十分な人もいます。単純なローカル環境で、configの接続先誤りが原因なら、その場で終わりです。
見分け方は、起動直後だけ失敗するのか、稼働中にも再現するのか。
- 起動直後だけ: healthcheckと依存条件を確認
- 稼働中にも発生: アプリの再試行、資源、外部依存を確認
前提が違うのに同じ薬を出さない。トラブルシュートでは、これがめっちゃ大事です。
チームへ入れるなら、破壊操作より先に4確認を固定する
トラブル時に焦ると、つい次のような操作を先に試したくなります。
docker compose down -v
docker system prune
でも、volume削除や広いpruneはデータや調査証拠を失う可能性があります。最初の一手には向きません。
先に固定したいのは、読み取り中心の4確認です。
1. docker compose config -q
2. docker compose config
3. docker compose ps --all --format json
4. docker compose logs --tail 100 --timestamps <services...>
これをリポジトリのトラブルシュート文書へ置き、次の項目だけチーム固有にします。
- 正常なサービス一覧
- readyの判定方法
- ログで匿名化する値
- 実行してよい環境
- 破壊的操作の承認者
- 10分で絞れない場合の相談先
最初から完璧なRunbookにしなくても大丈夫です。次に同じ故障が起きたとき、1行だけ更新できればいい。
診断手順を持つことの価値は、何でも知っていることではありません。焦っているときにも、最初の10分を同じ順番で使えることです。
まとめ
Docker Composeの起動失敗で、いきなりYAMLを全部書き直す必要はありません。
まずは 「起動失敗の4確認」 を順番に使います。
-
docker compose configで適用設定を固定する -
docker compose logsで最初の失敗を拾う - healthcheckでrunningとreadyを分ける
- サービス名DNSで接続先を固定する
今日10分でやることは1つだけです。
手元のComposeディレクトリで docker compose config を実行し、サービス間接続のホスト名が localhost や固定IPになっていないか1箇所確認する。
直す前に、観測する。
AIがコードを書く速度が上がるほど、この小さな順番が人間の判断時間を守ってくれる気がします。
動作確認と未確認範囲
- 本文のCompose YAML 2例はRuby標準YAMLパーサーで構文確認済みです。
- 診断シェルは
bash -nで構文確認済みです。 - 本実行環境にDocker Composeがないため、
docker compose config、イメージ取得、コンテナ起動は動作未確認です。 - イメージやComposeの対応機能は利用環境で異なるため、導入前に手元の
docker compose versionと公式リファレンスを確認してください。
参考リンク(一次情報・2026-07-28確認)
- Control startup and shutdown order in Compose
- Networking in Compose
- docker compose config
- docker compose logs
- docker compose ps
- Compose services: healthcheck / depends_on
生成AI活用エンジニア&3児のパパ。AI×開発の実践知を毎日発信しています → X