Compose ファイルは、多くの場合、手元にあるシステムの説明のなかでいちばん正直なものです。ノート PC でも CI でも、ときには本番環境でも、実際に起動するのはこのファイルの中身です。一方で、それは YAML の壁でもあります。新しく入ったメンバーは、結局「どのサービスがどのサービスと通信しているのか」を聞くことになります。
このガイドでは、Compose ファイルを信頼できる構成図に変えていきます。Compose が実際に実行するファイルをレンダリングし、各キーを図の要素に対応させ、ファイルでは表現できない呼び出しを書き加え、図をファイルと同期させ続ける、という流れです。最後に、Archboard の無料ツールを使って最初の下書きを数秒で作ります。
例: 小さな shop スタック
これから図にする compose.yaml です。短いですが、よくある落とし穴はそろっています。2 つのネットワークに属するサービス、設定ファイルの中にしか存在しない依存関係、そして開発専用のツールです。
name: shop
services:
web:
image: nginx:1.27
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
networks: [frontend]
api:
build: ./api
environment:
DATABASE_URL: postgres://shop:${POSTGRES_PASSWORD}@db:5432/shop
REDIS_URL: redis://cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks: [frontend, backend]
worker:
build: ./api
command: ["node", "worker.js"]
environment:
DATABASE_URL: postgres://shop:${POSTGRES_PASSWORD}@db:5432/shop
REDIS_URL: redis://cache:6379/0
depends_on:
- db
deploy:
replicas: 2
networks: [backend]
db:
image: postgres:16
environment:
POSTGRES_USER: shop
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shop"]
interval: 5s
networks: [backend]
cache:
image: redis:7
networks: [backend]
adminer:
image: adminer
ports:
- "8081:8080"
profiles: [debug]
networks: [backend]
volumes:
db-data:
networks:
frontend:
backend:
ステップ 1: Compose が実際に実行するファイルをレンダリングする
書かれたとおりのファイルを 1 つだけ実行しているチームはまれです。override ファイルがポートを追加し、.env ファイルが変数を埋め、extends が別のサービスからフィールドを取り込みます。描き始める前に、Compose に結果を出力させましょう。
docker compose config > rendered.yaml
docker compose config は override ファイルをマージし、変数を展開し、extends を解決したうえで、Compose が実行するモデルを出力します。たまたま開いているファイルではなく、この出力をもとに描いてください。
注意点が 1 つあります。出力には、.env 内の秘密情報も含め、変数の実際の値が入っています。rendered.yaml は .env ファイルそのものと同じように扱い、コミットも共有もしないでください。
ステップ 2: Compose のキーを図の要素に対応させる
図の大部分は、ファイルからそのまま読み取れます。
| Compose のキー | 図の要素 |
|---|---|
services.<name> |
サービスごとに 1 枚のカード。名前はキー名にする |
image / build |
カード上に、イメージのタグまたはビルドコンテキスト |
ports (8080:80) |
カード上と、外部から入るエッジに :8080 → 80 |
deploy.replicas / scale |
カード上のレプリカ数。ボックスは増やさない |
networks |
ネットワークごとに 1 つのフレーム。各サービスは最初のネットワークのフレームに置く |
名前付きの volumes |
ボリュームのノード。マウントしているサービスとつなぐ |
secrets、configs |
使っているサービスとつないだノード(名前のみ) |
depends_on |
起動順序。カードに書くか、破線のエッジにする |
profiles |
オプションのサービス。省くか、オプションであることを明記する |
図を正直に保つためのルールが 2 つあります。api のように 2 つのネットワークに参加するサービスは、両者をつなぐ橋です。最初のネットワークのフレームの端に置き、接続がもう一方のネットワークへまたがるようにします。また、レプリカは数であって、ボックスの山ではありません。worker ×2 は、5 にスケールしても正しいままです。
ステップ 3: ファイルに表れない呼び出しを加える
これは、ツールが代わりにやってくれないステップです。depends_on が扱うのは起動順序です。Compose は db を api より先に起動し、condition: service_healthy が指定されていればヘルスチェックが通るまで待ちます。実行時に誰が誰を呼び出すかについては、何も表していません。
実際の呼び出しは 3 か所に隠れています。
- 接続文字列。
DATABASE_URLとREDIS_URLは、ホスト(db、cache)とポートを指定しています。その 1 つひとつがエッジです。 - コンテナにマウントされた設定ファイル。
nginx.confにはproxy_pass http://api:3000;と書かれています。これがwebからapiへのエッジで、Compose ファイルの外にあります。 - コード。 Compose に 1 行も書かれていなくても、あるサービスが別のサービスを呼び出すことがあります。
workerはREDIS_URLを使っていますが、cacheへのdepends_onはありません。Redis の起動が遅れると worker は起動時に失敗します。そのすき間が最初に見えるのが、構成図です。
簡単な検索で、ほとんどは見つかります。
grep -rnE "://([^/@[:space:]]*@)?(api|web|db|cache)[:/]|proxy_pass" --include="*.conf" --include="*.yaml" --include="*.yml" --include="*.env" .
そのうえで、各サービスのオーナーに確認しましょう。実行時の呼び出しはプロトコルとポートをラベルにした実線のエッジで描き、depends_on はメモか破線のエッジとして残します。そうすれば、起動順序をトラフィックと取り違える人はいなくなります。
ステップ 4: 何を省くかを決める
Compose ファイルには、システムそのものと、その周りのツールが混在しています。通常は次のものを省きます。
- 開発専用のサービス。
profilesで切り替えるadminer、メールキャッチャー、デバッグ用プロキシなどです。 - 単発のジョブ。 マイグレーションやシーダーなどです。ただし、デプロイについての図なら別です。
- ソースコードや設定ファイルのバインドマウント。 設定ファイルは、それがもたらす依存関係を通してだけ示します。この例なら
nginx.conf由来のproxy_passのエッジです。 - 環境変数の値。 変数名はエッジの説明に役立ちますが、値はシークレットストアに置くもので、図に載せるものではありません。
ステップ 5: レイアウトを組む
トラフィックは左から右へ並べます。ブラウザー、公開ポート、その後ろのサービスの順です。ネットワークはそれぞれフレームとして描くと、ネットワークをまたぐ呼び出しがひと目でわかります。データストアはひとまとめにし、ボリュームはそれをマウントするサービスの下に置きます。すべてのエッジに、HTTP :3000 や SQL :5432 のようにプロトコルとポートを書きましょう。
無料ツールで最初の下書きを作る
カードを手で描く必要はありません。ファイル(またはレンダリング結果。ツールは環境変数の値を読み取りません)を、無料ツール「Docker Compose を図に変換」に貼り付けてください。表示だけならアカウントは不要です。サービスはイメージ、ポート、レプリカ数を載せたカードになり、ネットワークはフレームになります。名前付きボリューム、secret、config はそれを使うサービスにつながり、depends_on はサービス間の接続になります。
ツールが描くのは、ファイルに書かれていることだけです。実行時の呼び出しは表示せず、環境変数の値や env_file の中身も読み取らず、include もたどりません。extends を使うサービスでは、そのサービスに書かれたフィールドだけを描きます。だからこそ、ステップ 1 と 3 は今でも重要なのです。
無料アカウントがあれば、結果を Archboard で新しい図として開けます。そこでステップ 3 の実行時のエッジを加え、ステップ 4 のノイズを取り除きます。その作業を終えた shop スタックがこちらです。
ダイアグラムの内容
- Browser
- frontend
- backend
- web
- api
- cache
- db
- worker
- db-data
- HTTP :8080 → 80
- proxy_pass :3000
- SQL :5432
- RESP :6379
- jobs :6379
- /var/lib/postgresql/data
カードは Docker パック、ブラウザーはネットワークパックのもので、図全体が Free プランの上限であるダイアグラムノード 15 個に収まっています。コンテナ、レジストリ、ロードバランサーを含む別の Docker レイアウトは、Docker Runtime Mesh の例を参照してください。
最新に保つ
Compose ファイルから描いた図は、誰かがサービスを追加したその日に古くなります。次の 3 つの習慣が役立ちます。
- プルリクエストのテンプレートに 1 行加えます。「サービス、ネットワーク、公開ポート、接続文字列を追加しましたか? 図を更新してください。」
- レビューのときに
docker compose configを再実行し、サービスの一覧を図と比較します。 - README にはスクリーンショットを貼らず、ライブの図へのリンクを置きます。そうすれば、更新するコピーは 1 つだけで済みます。
スタックが Kubernetes に移ったら、同じ方法をマニフェストに適用できます。「Kubernetes YAML から構成図を作る方法」を参照してください。
よくある質問
depends_on は、あるサービスが別のサービスを呼び出すという意味ですか?
いいえ。起動順序を制御するもので、condition: service_healthy を付けるとヘルスチェックも待ちます。実行時の呼び出しは、接続文字列、設定ファイル、コードからわかります。
複数の Compose ファイルをまとめて図にできますか?
まず docker compose config でレンダリングしてください。override ファイルが 1 つのモデルにマージされます。無料ツールが一度に読み込むのは 1 ファイルです。
ツールは .env ファイルを読み取りますか?
いいえ。変数は展開されません。image: shop-api:${TAG} のように変数を使って書かれたイメージタグやポートは書かれたとおりに表示され、environment の値や env_file の中身も読み取りません。
コンテナごとにボックスを描くべきですか? サービスごとにカードを 1 枚描き、そこにレプリカ数を書きます。個々のコンテナは生まれては消えていくもので、チームが考える単位はサービスです。
compose.yaml を変更したら、図も更新されますか? いいえ。図は出発点です。上で紹介した習慣で最新に保つか、ツールで描き直して実行時のエッジを移してください。