0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker ComposeでAI生成ローカル環境の起動失敗を10分で直す — 4確認の診断順

0
Posted at

Docker ComposeでAI生成ローカル環境の起動失敗を10分で直す — 4確認の診断順

AIに「APIとPostgreSQLをDocker Composeで動かして」と頼むと、数十秒でそれらしいYAMLが返ってきます。

ところが docker compose up を実行すると、DBは動いているように見えるのにAPIだけ落ちる。もう一度起動すると、なぜか今度は動く。AIへログを貼るたびに修正箇所が増えて、最後はYAMLを全部書き直している。

なんかこれ、実装時間は短くなったのに、原因を推測する時間が増えてしまってるんですよね。

先に結論です。

Docker Composeの起動失敗は、次の4つを順番に確認すると、推測修正をかなり減らせます。

  1. docker compose config で「実際に適用される設定」を見る
  2. docker compose logs で「最初の失敗」を見る
  3. healthcheckで「起動済み」と「準備完了」を分ける
  4. サービス名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点です。

  1. 想定したサービスが存在するか
  2. 接続先ホスト名が localhost や固定IPになっていないか
  3. コンテナ間通信にホスト側ポートを使っていないか
  4. 環境変数が空文字や想定外の値へ展開されていないか

たとえば 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 で時刻を付けます。

見る順番はこうです。

  1. APIが最初に失敗した時刻
  2. その直前のDBログ
  3. エラーの種類
  4. 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つです。

  1. db.healthcheck がDBの準備完了を判定する
  2. api.depends_on.db.conditionservice_healthy を待つ

$$ とドル記号を2つ書いているのは、Compose側ではなくコンテナ内のシェルで POSTGRES_USERPOSTGRES_DB を展開させるためです。

状態は次のコマンドで確認できます。

docker compose ps --all --format json

Docker公式によると、docker compose ps はComposeプロジェクトのコンテナ、現在状態、公開ポートを表示します。JSON形式では StateHealthExitCode も扱えるので、目視だけでなく診断スクリプトにもつなげられます。

#!/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確認」 を順番に使います。

  1. docker compose config で適用設定を固定する
  2. docker compose logs で最初の失敗を拾う
  3. healthcheckでrunningとreadyを分ける
  4. サービス名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確認)

生成AI活用エンジニア&3児のパパ。AI×開発の実践知を毎日発信しています → X

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?