Docker 中級 #3 docker compose 基礎 — web + db を一つのファイルで
ここまでのコマンドは全てコンテナ一つを扱いました。でも実際のアプリは web + db、または web + db + cache + worker のように複数コンテナの束です。これを一つのファイルに定義して一つのコマンドで回す道具が Docker Compose です。
Docker 中級 シリーズでこの記事の位置:
- #1 マルチステージビルドとイメージスリミング
- #2 ビルドキャッシュ — レイヤー順序の最適化
- #3 docker compose 基礎 — web + db ← この記事
- #4 compose 深掘り — depends_on, healthcheck, profiles
- #5 環境変数と secrets 管理
- #6 ロギングとデバッグ
docker-compose と docker compose
#
最初に見ると混乱しやすい点から。二つの表記があります。
docker-compose(ハイフン) — 旧 v1、Python で作られた別バイナリ。deprecated。docker compose(スペース) — 現在 v2、Go で作られて Docker に統合。これが標準。
この記事は docker compose (v2) で統一します。Docker Desktop には標準で含まれ、Linux では docker-compose-plugin パッケージがあります。
docker compose version
# Docker Compose version v2.30.xなぜ Compose が必要か #
docker run でやっていたことをそのまま移すとすぐ限界が見えます。
docker network create myapp-net
docker volume create pgdata
docker run -d --name pg \
--network myapp-net \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16
docker run -d --name web \
--network myapp-net \
-p 8000:8000 \
-e DB_HOST=pg \
-e DB_PASSWORD=secret \
myapp:latestこれくらいが「最小」セットアップです。環境がもっと増えるとシェルスクリプトに移しますが、それでもどこかに変更が出ると最初から立ち上げ直すことになり、同僚にセットアップを教えるのに README が長くなります。
同じことを Compose で書くと:
services:
pg:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
web:
image: myapp:latest
ports:
- "8000:8000"
environment:
DB_HOST: pg
DB_PASSWORD: secret
depends_on:
- pg
volumes:
pgdata:docker compose up 一つのコマンドで全体起動。変更があれば同じコマンドで変更された部分だけ作り直し。同僚には「リポジトリをクローンして docker compose up」一行で終わります。
ファイル名と位置 #
標準ファイル名は compose.yaml (または compose.yml) です。昔よく見た docker-compose.yml も今でも動きますが、新規プロジェクトなら compose.yaml 推奨。
compose.yaml # 1 番目
compose.yml # 2
docker-compose.yaml # 3
docker-compose.yml # 4 (古い慣習)コマンドはファイルがあるディレクトリで単に docker compose up — パス指定がなければ上の順序で探します。ファイルが別の場所にあるなら -f で:
docker compose -f infra/compose.yaml up
docker compose -f compose.yaml -f compose.dev.yaml up # 複数 (override)compose.yaml の大枠 #
トップレベルキー一覧。
| トップレベルキー | 意味 |
|---|---|
services | コンテナ群 (一番よく書くところ) |
volumes | 名前付きボリューム |
networks | 名前付きネットワーク |
secrets | secret (#5 で) |
configs | コンテナに注入する設定ファイル |
古いファイルでよく見る version: "3.8" のような行は もう使いません。 Compose v2 が無視します。新しいファイルでは外す方が綺麗。
実践例 — Django + Postgres + Redis #
もっと現実的な例を組みます。Django 実戦シリーズ のようなセットアップ。
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./:/app
ports:
- "8000:8000"
environment:
DATABASE_URL: postgres://app:secret@pg:5432/app
REDIS_URL: redis://redis:6379/0
DEBUG: "1"
depends_on:
- pg
- redis
pg:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432" # ホストの DB クライアントからアクセスする場合
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:各項目を解きほぐすと:
build: .— カレントディレクトリのDockerfileでビルド。イメージを直接作るときimage: postgres:16— イメージを取って使うときcommand:— イメージのデフォルト CMD をオーバーライドvolumes:— bind mount (./:/app) と named volume (pgdata:/var/...) が一箇所に混じっています。:前が絶対パス / 相対パスなら bind、ただの名前なら named。ports:—docker run -pと同じ形。"127.0.0.1:5432:5432"のようにホスト IP も可能。environment:—-e KEY=valに相当。マッピングまたはリストで書けるdepends_on:— 開始順序 (#4 で深く)
Compose が自動でやってくれること #
docker run で一つずつやっていたことを Compose が自動でやります。
自動ネットワーク #
compose up の最初の実行時に プロジェクト名のデフォルトネットワーク を作って全サービスをそのネットワークにぶら下げます。
docker network ls
# NAME DRIVER SCOPE
# myapp_default bridge local同じネットワークの中のサービスは サービス名でお互いを呼べます。 上の例で web コンテナの環境変数 DATABASE_URL: postgres://app:secret@pg:5432/app — ホストが pg なんですが、これがそのまま動きます。(基礎 #4 のユーザー定義 bridge ネットワーク + DNS と同じ原理。)
プロジェクト名前空間 #
Compose は全リソースに プロジェクト名 を prefix として付けます。
docker ps
# NAMES
# myapp-web-1
# myapp-pg-1
# myapp-redis-1デフォルトプロジェクト名はディレクトリ名。変更するなら -p:
docker compose -p myproj up
# myproj-web-1, myproj-pg-1 ...同じマシンで同じ compose ファイルを別の名前で二つ立ち上げられます。テスト用 / 開発用環境を同時に回すときに便利です。
日常コマンド群 #
docker compose up # フォアグラウンド + 全ログを一箇所で
docker compose up -d # バックグラウンド (detached)
docker compose up --build # ビルドからやり直し (コード変更後)
docker compose up web # 特定サービスだけ
docker compose up --remove-orphans # compose.yaml から外れた古いサービスも片付けるdocker compose down # コンテナ + ネットワーク削除
docker compose down -v # 上 + ボリュームまで (データが消える)
docker compose down --rmi local # 上 + ビルドされたイメージまでdocker compose ps # このプロジェクトのコンテナだけ
docker compose ps -a # 終了したものまでdocker compose logs # 全サービスのログを一箇所で
docker compose logs -f # follow
docker compose logs -f web # 特定サービスだけ
docker compose logs --since 10m # 直近 10 分docker compose exec web bash # 立ち上がっている web の中で bash
docker compose run --rm web python manage.py migrate # 新しいコンテナで一回限りのコマンドexec と run の違いは 基礎 #3 の docker exec vs docker run の違いと同じです。マイグレーション / シードのような一回限りの作業は run --rm、立ち上がっているコンテナのデバッグは exec。
docker compose restart web # 一サービス再起動
docker compose stop # 止めるがコンテナは保存 (down と違う)
docker compose start # 止まっているのを再び起動up vs start vs restart
#
三つのコマンドが似て見えますが意味が違います。
up— コンテナを作る (なければ)、変更を反映して作り直す (あっても定義が変わっていれば)start— 止まった (既存) コンテナを起動。定義変更は反映しないrestart—stop後start。定義変更反映なし
compose.yaml を編集した後は常に up です。
build: 深掘り
#
サービスでイメージを直接ビルドするとき:
services:
web:
build:
context: . # ビルドコンテキスト (Dockerfile がある場所)
dockerfile: Dockerfile.dev # デフォルト以外の別ファイルを使うとき
args:
APP_VERSION: "1.0.0"
target: dev # マルチステージの特定ステージ
cache_from:
- myapp:cache
image: myapp:dev # ビルド結果にこの名前を付けるimage: と build: を一緒に書くと、ビルドした成果物にその名前を付けます。すると docker compose push でそのイメージをレジストリにアップロードもできます。
volumes: の整理
#
サービスの volumes: は三つの形式がよく見えます。
services:
web:
volumes:
- ./:/app # bind (相対パス)
- /Users/me/data:/app/data # bind (絶対パス)
- pgdata:/var/lib/postgresql/data # named (トップレベル volumes で定義)
- logs:/app/logs:ro # named, read-only
- type: bind
source: ./config
target: /etc/myapp
read_only: true # 長い形式
volumes:
pgdata:
logs:短い形式が日常では十分で、読み取り専用 / 権限 / オプションがもっと必要なとき 長い形式に展開して書きます。
networks: — ユーザー定義
#
デフォルトネットワーク以外にユーザー定義ネットワークも作れます。一つのプロジェクトの中で一部サービスだけ同じネットワークに置きたいとき。
services:
web:
networks:
- frontend
- backend
pg:
networks:
- backend
nginx:
networks:
- frontend
networks:
frontend:
backend:pg は backend だけにあるので nginx が直接呼べません。セキュリティ隔離に有用なパターンです。ただし一般のアプリではデフォルトネットワーク一つで十分です。
よく出会う落とし穴 #
- ログが見えない — アプリが
127.0.0.1にバインドした、またはファイルにログを書いています。0.0.0.0バインド + stdout 出力に。 - DB は全部立ち上がったのに web で connection refused —
depends_onはコンテナの開始順序だけを見る。DB が 準備 できたかは見ません。(#4 の healthcheck で解く。) - bind mount したコードをコンテナが見られない — Docker Desktop のファイル共有設定で該当ディレクトリが外れている可能性。
Settings → Resources → File Sharingを確認。 compose downで DB データが消えた —-vを一緒に使うと named volume まで消える。普段は-vなしで。build:したのに変更が反映されない —up --buildまたはup -d --buildで。
一サイクル #
新しいプロジェクトの日常:
# 最初
docker compose up -d --build
# コード編集 (bind mount なら自動反映、dev サーバなら自動再起動)
docker compose logs -f web
# マイグレーション
docker compose run --rm web python manage.py migrate
# DB シェル進入
docker compose exec pg psql -U app
# 一日の終わり
docker compose down
# 最初からやり直し (DB データも飛ばして)
docker compose down -v
docker compose up -d --buildこれくらいが手に馴染めば、一つのプロジェクトのインフラセットアップが一つのファイル + 数行のコマンドにまとまります。
まとめ #
この記事で掴んだ絵:
docker compose(v2) が標準。docker-compose(v1) は deprecatedcompose.yaml一つのファイルにservices/volumes/networksを定義 — 一つのコマンドで立ち上げ- サービス同士は サービス名が DNS のように動作 (自動ネットワーク + ユーザー定義 bridge)
build:+image:でビルドとタグを一箇所で- 日常コマンド:
up -d、down、logs -f、exec、run --rm upは変更反映、startは単純起動 —compose.yamlを変えたら常にup
次の記事 (#4 compose 深掘り — depends_on, healthcheck, profiles) ではこの骨格に運用道具を重ねます。healthcheck で「DB が本当に準備できたか」を見て、depends_on の condition で意味のある開始順序を立て、profiles で dev / test / prod を一つのファイルの中で分岐する方法です。