compose.yamlを1行ずつ解剖
「depends_onを書いたのにDB接続で落ちる」事故を根絶できる。Webアプリ+DB+キャッシュの実例を1行ずつ精読し、ports文字列化の理由からhealthcheck連携、ボリュームの使い分けまで根拠つきで理解できる。
- Compose v2ではトップレベルのversion:は不要で、書いても無視される。services:で既製イメージならimage、Dockerfileから組むならbuildを指定し、併記時は結果をimage名で保存する。
- ports:は引用符付き文字列で書く。YAML 1.1系はコロン区切りを60進数と解釈し、22:22が整数1342へ化ける恐れがあるため、Compose公式も文字列表記を推奨する。
- depends_on:が保証するのは起動順だけで、接続可能になるまで待たない。依存先にhealthcheckを定義し、呼び出し側でcondition: service_healthyを指定して準備完了を待つ。
この設定ファイルは何者か
compose.yamlは、複数のコンテナからなるアプリケーション一式を1枚のYAMLで宣言する設定ファイルだ。Webアプリ、DB、キャッシュといった構成要素を「サービス」として列挙しておけば、docker compose upの一撃で、ネットワークもボリュームも含めた環境がまるごと立ち上がる。起動手順を文書で人間に伝える代わりに構成そのものをコードで管理する、DevOpsの実践で最初に触れる宣言的設定のひとつだ。
コンテナが1つならdocker runで足りる。だが現実の開発環境は複数コンテナの協調が前提で、起動順序・接続情報・永続化を毎回コマンドで再現するのは無理がある。その「構成の記憶」を引き受けるのがこのファイルだ。
Compose v2の推奨ファイル名はcompose.yaml(compose.ymlも可)。旧来のdocker-compose.ymlも後方互換のため読み込まれるが、両方が存在する場合はcompose.yamlが優先される。
横にスクロール
1行ずつ解剖
題材は、Webアプリ(web)+PostgreSQL(db)+Redis(cache)という現実的な3サービス構成だ。まず全体を眺めてほしい。
services:
web:
build: .
ports:
- "8080:3000"
env_file:
- web.env
environment:
- DATABASE_URL=postgres://app:secret@db:5432/appdb
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 5s
cache:
image: redis:7
volumes:
db-data:
上から順に、キーごとに読み解いていく。
services — imageとbuildの使い分け
servicesはこのファイルの主役で、直下に並ぶキー名(web・db・cache)がそのままサービス名になる。サービスは「同じ設定で起動されるコンテナの定義」で、基本はサービス1つにつきコンテナ1つが起動される。
イメージの入手経路は2通りある。dbとcacheのようにimageを書けば、レジストリから既製イメージ(postgres:16やredis:7)を取得する。webのようにbuildを書けば、指定ディレクトリ(.はカレント)のDockerfileからイメージを組み立てる。自作アプリはbuild、ミドルウェアはimageという分担が定石だ。両方を併記した場合は、buildの成果物がimageで指定した名前で保存される。Dockerfile側の読み方はDockerfileの解剖で扱う。
ports — 「ホスト:コンテナ」を必ず文字列で書く理由
"8080:3000"は「ホストの8080番に届いた通信をコンテナの3000番へ転送する」という対応で、コロンの左がホスト側、右がコンテナ側だ。左側を省略すれば、ホスト側は空きポートが自動で割り当てられる。
見逃せないのが引用符である。YAML 1.1系のパーサには、コロン区切りの数値列を60進数として解釈する仕様があり、引用符のない22:22は文字列ではなく整数1342(22×60+22)として読み込まれてしまう。この解釈はコロンの後ろ側が60未満の数のときに成立するため、小さいポート番号ほど危ない。Compose公式ドキュメントも、ポートの対応は常に文字列で書くことを推奨している。化けた後のエラーは原因と結びつけにくいので、無条件に引用符を付ける習慣にしたい。
なお、portsは「ホストから触るための口」であって、コンテナ同士の通信には不要だ。webがdbの5432番へ接続するのに、dbのportsを開ける必要はない。
volumes — 名前付きボリュームとバインドマウント
dbのdb-data:/var/lib/postgresql/dataは名前付きボリュームの指定だ。最終行のトップレベルvolumes:で宣言したdb-dataという領域を、コンテナ内のデータディレクトリへ差し込んでいる。実体はDockerの管理領域に置かれ、コンテナを削除してもデータは残る。データベースの永続化はこの形が基本になる(DB自体の学習はデータベースへ)。
これに対し、./src:/app/srcのようにホストのパスを直接対応付ける書き方をバインドマウントと呼ぶ。ソースコードをコンテナと共有し、編集を即座に反映させる開発用途の主役だ。
| 観点 | 名前付きボリューム | バインドマウント |
|---|---|---|
| 書き方の例 | db-data:/var/lib/postgresql/data | ./src:/app/src |
| 実体の置き場所 | Dockerの管理領域 | ホストの指定パス |
| トップレベルvolumes:での宣言 | 必要 | 不要 |
| 主な用途 | DBデータなどの永続化 | ソースコード共有・ホットリロード |
environmentとenv_file — 環境変数の2つの渡し方
environmentは、コンテナへ渡す環境変数をその場に書く。リスト形式(- KEY=value)とマップ形式(KEY: value)のどちらでもよく、サンプルはwebで前者、dbで後者を使っている。webのDATABASE_URLのホスト名がdbになっている点は、後述するサービス名DNSの伏線だ。
env_fileは、変数を外部ファイル(ここではweb.env)にまとめて読み込む。環境ごとの差し替えや、リポジトリに置きたくない値の分離に向く。同じ変数を両方で定義した場合は、environmentの値が優先される。
紛らわしいのがプロジェクト直下の.envファイルで、これはenv_fileとは別の仕組みだ。.envはcompose.yaml自体の変数展開(値の穴埋め)に使われるもので、コンテナへ自動では渡らない。
depends_onとhealthcheck — 起動順序と準備完了は別物
depends_onはサービス間の依存を宣言し、起動と停止の順序を制御する。webはdbとcacheの後に起動し、停止はその逆順になる。docker compose up webのように単体を指定しても、依存先のdbとcacheは一緒に起動される。
ここが最重要だ。素のdepends_on(condition: service_started相当)が待つのは「依存先のコンテナが起動したこと」までで、中のプロセスが接続を受け付けられる状態かは一切見ていない。PostgreSQLのようにプロセス起動から受け付け開始まで時間差があるものでは、webが先に接続しにいって失敗する。
そこでdbにhealthcheckを定義し、pg_isreadyで「接続を受け付けられるか」を5秒間隔で検査させている。web側はdbにcondition: service_healthyを指定しているので、この検査が通るまで起動が保留される。順序の制御(depends_on)と準備完了の判定(healthcheck)は、組み合わせて初めて「待ったこと」になる。cacheは起動が速いRedisなので、既定と同じservice_startedの明示にとどめた。
condition: service_healthyを指定したのに、依存先にhealthcheckが定義されていない(イメージ組み込みのHEALTHCHECKもない)場合は、健全性を判定する材料がなく起動が失敗する。必ずセットで書くこと。
conditionはservice_started(既定)、service_healthy、マイグレーションなど一度きりの処理の正常終了を待つservice_completed_successfullyの3種類だ。
networks — 何も書いていないのに繋がる理由
このサンプルにnetworks:は一度も登場しないのに、webはdbともcacheとも通信できる。Composeがプロジェクトごとに「プロジェクト名_default」というネットワークを自動作成し、全サービスを参加させるからだ。このネットワーク内では、サービス名がそのままDNS名として解決される。DATABASE_URLのホスト名がdbで済むのはこのためで、IPアドレスを1つも書かずに構成が完結する。到達範囲を分けたいときにだけ、明示的にnetworks:を定義すればよい。
restart — 落ちたあとの身の振り方
restartはコンテナ停止時の再起動ポリシーだ。既定はno(再起動しない)で、ほかにalways、異常終了時だけ再起動するon-failure、手動停止した場合を除き常に再起動するunless-stoppedがある。webのunless-stoppedは、ホスト再起動後に復帰しつつ、自分で止めたものは勝手に蘇らない、開発機や小規模運用の定番だ。
version: — もう書かなくてよい
古い記事のサンプルはversion: '3.8'のような行から始まるが、この30行にないのは書き忘れではない。Compose v2はCompose Specificationに基づいて解釈するため、トップレベルのversionは廃止(obsolete)扱いで、書いても無視されて警告が出るだけだ。新規ファイルでは省くのが正しい。
つまずきやすい点
depends_onで待ったつもりになる。 「depends_onを書いたのにwebがDB接続エラーで落ちる」は、この設定ファイル最大の定番事故だ。前述の通り、素のdepends_onは起動順序しか保証しない。依存先のhealthcheckとcondition: service_healthyの組み合わせに直すか、アプリ側に接続リトライを実装する。両方やるのが堅い。
バインドマウントでnode_modulesが消える。 Dockerfile内でnpm installした後、開発用にプロジェクト全体を./:/appでバインドマウントすると、イメージ内に作られた/app/node_modulesがホスト側の空のディレクトリに覆い隠され、モジュール不明のエラーになる。定番の回避策は、バインドマウントの内側に匿名ボリュームを重ね、node_modulesだけコンテナ側の実体を生かすことだ。
volumes:
- ./:/app
- /app/node_modules
引用符なしのportsが60進数に化ける。 引用符を忘れたポート指定はYAMLの60進数解釈で整数に変わることがあり、起動自体は成功する分だけ発見が遅れる。コロンを含む値は文字列にする、と機械的に覚えたい。
docker composeとdocker-composeは別物。 スペース区切りのdocker composeはGo製のCompose v2(Docker CLIのプラグイン)、ハイフン区切りのdocker-composeはPython製のv1で、v1は2023年7月に更新を終えている。古い手順書に従う前に、docker compose versionでv2が使える環境かを確認したい。
| 項目 | docker compose(v2) | docker-compose(v1) |
|---|---|---|
| 実体 | Docker CLIプラグイン(Go製) | 独立コマンド(Python製) |
| 状態 | 現行。Compose Specification準拠 | 2023年7月に更新終了 |
| version:キー | 廃止扱いで無視される | スキーマの切り替えに使用 |
まとめ
compose.yamlの本質は、複数コンテナの構成をコマンドの記憶ではなくファイルの宣言に置き換えることだ。imageとbuildの分担、文字列で書くports、永続化を担う名前付きボリューム、サービス名で引けるネットワーク、そしてdepends_onとhealthcheckの二人三脚——この30行に複数コンテナ運用の急所が一通り詰まっている。「起動順序と準備完了は別物」の一点を持ち帰るだけでも、初見のトラブルの多くに説明がつくはずだ。
設定ファイル解剖の記事ガイド
compose.yamlを1行ずつ解剖を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
設定ファイル
比較で見る軸
難易度: advanced / カテゴリ: 設定ファイル解剖 / タグ数: 4
導入後に効く点
ports:は引用符付き文字列で書く。YAML 1.1系はコロン区切りを60進数と解釈し、22:22が整数1342へ化ける恐れがあるため、Compose公式も文字列表記を推奨する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 設定ファイル解剖
- タグ数
- 4
判断チェックリスト
- 自社の用途が「設定ファイル / Docker」に近いか確認する。
- 強みである「Compose v2ではトップレベルのversion:は不要で、書いても無視される。services:で既製イメージならimage、Dockerfileから組むならbuildを指定し、併記時は結果をimage名で保存する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。