Docker Compose入門:複数コンテナを一括管理する基本と実践

Docker Composeの基本概念からdocker-compose.ymlの書き方、頻出コマンドまでを初心者向けに解説。複数コンテナの一括管理を実践的なコード例とともに学べます。
代表 / エンジニア
Dockerでコンテナを1つ動かすことはできたけれど、WebサーバーとDBを組み合わせた途端に手順が複雑になった――そんな経験をお持ちの方は多いのではないでしょうか。本記事では、複数コンテナの管理を劇的にシンプルにするDocker Composeの基本を、実践的なコード例とともに解説します。
Docker Composeとは何か
Docker Composeは、複数のDockerコンテナをまとめて定義・起動・管理するためのツールです。通常、Dockerで複数のコンテナを連携させるには、docker runコマンドをコンテナごとに実行し、ネットワークやボリュームを手動で設定する必要があります。
筆者も最初はコンテナを1つずつ起動していたのですが、サービスが3つ、4つと増えるにつれて起動順序やネットワーク設定の管理が煩雑になり、「もっと楽な方法があるはずだ」と感じたのがCompose導入のきっかけでした。
Docker Composeを使えば、docker-compose.yml(現在はcompose.ymlも可)というYAMLファイル1つにすべてのサービス構成を宣言的に記述できます。docker compose upの一発で環境全体が立ち上がるため、開発環境の構築時間を大幅に短縮できます。
Docker Composeが特に活きる場面
Docker Composeはあらゆる場面で使えますが、特に効果を発揮するのは以下のようなケースです。
- チーム開発での環境統一: 新しいメンバーがジョインしたとき、READMEに「
docker compose up -dを実行してください」と一行書いておくだけで環境構築が完了します。OSの違いやローカルにインストール済みのツールのバージョン差異に悩まされることもありません。 - マイクロサービス構成の開発: API Gateway、認証サービス、メインアプリ、DBなど複数のサービスが連携するアーキテクチャでは、個別にコンテナを管理するのは現実的ではありません。Composeなら全体を俯瞰しながら管理できます。
- CI/CDパイプラインでの統合テスト: テスト実行時にDBやキャッシュサーバーを一時的に立ち上げ、テスト完了後にまとめて破棄する、という流れをシンプルに自動化できます。
筆者がComposeの恩恵を最も強く感じたのは、あるプロジェクトで開発メンバーが3名から8名に増えたときでした。それまではセットアップ手順書を口頭で補足しながら共有していたのですが、Compose導入後はYAMLファイルを見れば環境の全体像がわかるため、オンボーディングにかかる時間が大幅に短縮されました。
Docker Composeを使わない場合との比較
Composeの便利さを実感するには、使わない場合の手順と比べるのが一番です。たとえば、Next.jsアプリとPostgreSQLを連携させるケースを考えてみましょう。
Composeなしの場合、以下のコマンドを順番に実行する必要があります。
# 1. 専用ネットワークを作成
docker network create myapp-network
# 2. PostgreSQLコンテナを起動
docker run -d \
--name myapp-db \
--network myapp-network \
-e POSTGRES_USER=user \
-e POSTGRES_PASSWORD=pass \
-e POSTGRES_DB=mydb \
-v myapp-db-data:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16
# 3. DBの準備完了を手動で確認
docker exec myapp-db pg_isready -U user
# 4. アプリコンテナをビルド・起動
docker build -t myapp .
docker run -d \
--name myapp-app \
--network myapp-network \
-e DATABASE_URL=postgres://user:pass@myapp-db:5432/mydb \
-v $(pwd):/app \
-p 3000:3000 \
myappコンテナを停止・削除するときも、1つずつ操作が必要です。
docker stop myapp-app myapp-db
docker rm myapp-app myapp-db
docker network rm myapp-networkこれがComposeであれば、docker compose up -dとdocker compose downの2コマンドで済みます。サービスが増えるほどこの差は広がりますし、チームメンバーに「この順番でこのコマンドを打って」と口頭で伝える必要もなくなります。YAMLファイルそのものがドキュメントの役割を果たしてくれるのです。
Docker Compose V1とV2の違い
Docker Composeには歴史的にV1とV2の2つのバージョンが存在します。これが初学者を混乱させる一因になっているので、ここで整理しておきましょう。
| 項目 | V1(旧版) | V2(現行版) |
|---|---|---|
| コマンド | docker-compose(ハイフン付き) | docker compose(スペース区切り) |
| 実装言語 | Python | Go |
| インストール | 別途インストールが必要 | Docker Desktop / Docker CLIプラグインに同梱 |
| ステータス | 2023年にEOL(サポート終了) | 現在の推奨版 |
現在のDocker Desktopを使っていれば、V2がデフォルトで利用できます。インターネット上の情報を参照する際にdocker-compose(ハイフン付き)の記述を見かけたら、それはV1向けの情報だと認識してください。本記事ではすべてV2の記法(docker compose)で統一しています。
なお、設定ファイル名も従来のdocker-compose.ymlからcompose.ymlへと簡素化されました。どちらのファイル名でも動作しますが、新規プロジェクトではcompose.ymlを使うのがシンプルでよいでしょう。
compose.ymlの基本構造
実際のファイル構造を見るのが最も理解しやすいでしょう。以下は、Next.jsアプリケーションとPostgreSQLを組み合わせた典型的な構成例です。
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/mydb
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- node_modules:/app/node_modules
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 3s
retries: 5
volumes:
db_data:
node_modules:主要なキーの役割を以下の表にまとめました。この一覧は設定に迷ったときのリファレンスとしてお使いください。
| キー | 役割 | 記述例 |
|---|---|---|
services | コンテナの定義を列挙する最上位キー | services: |
image | 使用するDockerイメージを指定 | postgres:16 |
build | Dockerfileからビルドする場合のパス | . または ./app |
ports | ホストとコンテナのポートマッピング | "3000:3000" |
volumes | データの永続化やホストとの同期 | db_data:/var/lib/postgresql/data |
environment | 環境変数の設定 | POSTGRES_USER: user |
depends_on | サービスの起動順序を制御 | depends_on: [db] |
healthcheck | コンテナの正常性チェック定義 | test: ["CMD", "pg_isready"] |
振り返ると、最初はdepends_onだけで起動順序を制御できると思い込んでいたのですが、これはコンテナの起動順序を保証するだけで、アプリケーションの準備完了までは待ってくれません。condition: service_healthyとhealthcheckの組み合わせに気づくまで、接続エラーに悩まされました。
buildの詳細設定
先ほどの例ではbuild: .とシンプルに書きましたが、ビルド設定はより細かく指定することも可能です。モノレポ構成や、複数のDockerfileを使い分けるプロジェクトでは以下のような書き方が役立ちます。
services:
app:
build:
context: .
dockerfile: docker/Dockerfile.dev
args:
NODE_VERSION: "20"
target: development| キー | 役割 |
|---|---|
context | ビルドコンテキスト(Dockerデーモンに送信するファイル群のルートパス) |
dockerfile | 使用するDockerfileのパス(デフォルトはcontext内のDockerfile) |
args | ビルド時の引数(Dockerfile内のARGに対応) |
target | マルチステージビルドで使用するターゲットステージ名 |
targetはマルチステージビルドと組み合わせると非常に便利です。たとえば、1つのDockerfileに開発用と本番用のステージを定義しておき、compose.ymlから使い分けることができます。
# Dockerfile
FROM node:20-slim AS base
WORKDIR /app
COPY package*.json ./
FROM base AS development
RUN npm install
CMD ["npm", "run", "dev"]
FROM base AS production
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["npm", "start"]開発時はtarget: development、本番ではtarget: productionを指定すれば、Dockerfileを一元管理しながら環境に応じたイメージをビルドできます。筆者もかつては開発用と本番用のDockerfileを別々に管理していたのですが、変更のたびに両方を更新する手間が生じていました。マルチステージビルドに移行してからはその問題が解消され、メンテナンスがずいぶん楽になりました。
環境変数と.envファイルの活用
先ほどの例では環境変数をcompose.ymlに直接記述しましたが、パスワードなどの機密情報をYAMLファイルにハードコードするのは望ましくありません。実際のプロジェクトでは.envファイルと組み合わせて管理するのが一般的です。
まず、プロジェクトルートに.envファイルを作成します。
# .env
POSTGRES_USER=user
POSTGRES_PASSWORD=s3cret_passw0rd
POSTGRES_DB=mydb
APP_PORT=3000compose.ymlからは変数展開の構文${変数名}で参照できます。
services:
app:
build: .
ports:
- "${APP_PORT}:3000"
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
db:
image: postgres:16
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}.envファイルは必ず.gitignoreに追加し、バージョン管理の対象外にしてください。チームで共有する場合は.env.exampleとして変数名だけを記載したテンプレートをリポジトリに含めておくと親切です。
# .env.example(値は空欄にしてリポジトリに含める)
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
APP_PORT=3000また、env_fileキーを使えば、サービスごとに異なる環境変数ファイルを読み込ませることも可能です。
services:
app:
build: .
env_file:
- .env
- .env.app筆者の経験では、環境変数の管理方法はプロジェクト初期に決めておかないと、後から統一するのが大変です。小さなプロジェクトでも最初から.envファイルを使う習慣をつけておくことをおすすめします。
ネットワークの仕組み
Docker Composeは、デフォルトで同一compose.yml内のサービスが互いに通信できるネットワークを自動的に作成します。この仕組みのおかげで、先ほどの例のようにdb:5432というサービス名をホスト名として使えるわけです。
明示的にネットワークを定義することで、より細かい制御も可能です。たとえば、フロントエンドからはバックエンドにアクセスできるが、フロントエンドからDBには直接アクセスさせたくないといった場合は、ネットワークを分離できます。
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
networks:
- front-tier
backend:
build: ./backend
ports:
- "8080:8080"
networks:
- front-tier
- back-tier
db:
image: postgres:16
networks:
- back-tier
networks:
front-tier:
back-tier:この構成では、frontendはbackendと通信できますが、dbとは直接通信できません。backendは両方のネットワークに属しているため、frontendからのリクエストを受け取りつつdbにもアクセスできます。本番環境に近いセキュリティ設計を開発段階から意識できるのは、Composeならではの利点です。
ボリュームの種類と使い分け
compose.ymlで指定するボリュームには大きく2つの種類があります。使い分けを誤るとデータ消失やパフォーマンス低下の原因になるため、ここでしっかり理解しておきましょう。
名前付きボリューム(Named Volume)
volumes:
- db_data:/var/lib/postgresql/dataDocker Engineが管理する永続化領域にデータを保存します。コンテナを削除してもdocker compose down -vを実行しない限りデータは残ります。DBのデータや、再ビルド時にも保持したいnode_modulesなどに適しています。
バインドマウント(Bind Mount)
volumes:
- .:/appホストマシンのディレクトリをコンテナ内にマウントします。ホスト側でファイルを編集すると、コンテナ内にも即座に反映されるため、開発中のソースコードの同期に最適です。
筆者が初期に悩んだのは、node_modulesの扱いでした。ソースコード全体をバインドマウントすると、ホスト側のnode_modulesがコンテナ内のものを上書きしてしまい、ネイティブモジュールの不整合でエラーが発生することがあります。これを防ぐため、先ほどの例のようにnode_modulesだけは名前付きボリュームで分離するテクニックが定番です。
volumes:
- .:/app # ソースコードはバインドマウント
- node_modules:/app/node_modules # node_modulesは名前付きボリュームで分離tmpfsマウント
あまり紹介されることはありませんが、3つめの選択肢としてtmpfsマウントがあります。これはホストのメモリ上にマウントされる一時的な領域で、コンテナが停止すると内容は消えます。
services:
app:
build: .
tmpfs:
- /tmp
- /app/.cacheテスト実行時の一時ファイルやキャッシュなど、永続化する必要がなくディスクI/Oをボトルネックにしたくない場合に有用です。特にテストスイートの実行速度が気になるプロジェクトでは、一時ファイルの出力先をtmpfsに逃がすだけで体感速度が変わることがあります。
実践的な構成例:3サービス以上の連携
基本的な2サービス構成に慣れてきたら、実際のプロジェクトに近い構成にも挑戦してみましょう。以下は、Next.js + PostgreSQL + Redisの3サービス構成の例です。Redisはセッション管理やキャッシュとしてよく使われます。
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- .:/app
- node_modules:/app/node_modules
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
restart: unless-stopped
volumes:
db_data:
node_modules:
redis_data:いくつかの注目ポイントを補足します。
restart: unless-stoppedの活用
開発中にコンテナがエラーで停止してしまうと、いちいち手動で再起動するのは面倒です。restartポリシーを設定しておけば、予期しないクラッシュ時に自動で再起動してくれます。選択肢は以下の4つです。
| ポリシー | 動作 |
|---|---|
no | 再起動しない(デフォルト) |
always | 常に再起動 |
on-failure | 異常終了時のみ再起動 |
unless-stopped | 手動停止以外は再起動 |
開発環境ではunless-stoppedが使いやすいでしょう。alwaysにするとdocker compose stopで意図的に止めた後も再起動してしまうため、少々扱いにくくなります。
初期化SQLの自動実行
PostgreSQLの公式イメージは、/docker-entrypoint-initdb.d/に配置したSQLファイルを初回起動時に自動実行してくれます。テーブルの作成や初期データの投入を自動化できるため、開発環境のセットアップがさらにスムーズになります。
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql注意点として、この初期化処理はデータボリュームが空の場合にのみ実行されます。すでにデータが存在する場合はスキップされるため、初期化をやり直したいときはdocker compose down -vでボリュームを削除してから再起動してください。
なお、複数のSQLファイルやシェルスクリプトを配置した場合、ファイル名のアルファベット順に実行されます。実行順序を明示的に制御したいときは、ファイル名に連番プレフィックスを付けるとよいでしょう。
volumes:
- ./docker/initdb/01_create_tables.sql:/docker-entrypoint-initdb.d/01_create_tables.sql
- ./docker/initdb/02_seed_data.sql:/docker-entrypoint-initdb.d/02_seed_data.sql
- ./docker/initdb/03_create_indexes.sql:/docker-entrypoint-initdb.d/03_create_indexes.sql覚えておきたい基本コマンド
日常的に使うコマンドは意外と限られています。以下のコマンドを押さえておけば、基本的な操作で困ることはないでしょう。
# 全サービスをバックグラウンドで起動
docker compose up -d
# 全サービスを停止・コンテナ削除
docker compose down
# 停止時にボリュームも削除(DBデータ含む。開発リセット用)
docker compose down -v
# 特定サービスのログをリアルタイム表示
docker compose logs -f app
# サービスを再ビルドして起動
docker compose up -d --build
# 稼働中のコンテナ一覧を確認
docker compose ps
# 稼働中のコンテナ内でコマンドを実行
docker compose exec db psql -U user -d mydb特にdocker compose downとdocker compose down -vの違いは重要です。-vを付けるとボリュームごと削除されるため、DBのデータも消えます。本番データを扱う場面でうっかり実行しないよう注意が必要かもしれません。
開発で役立つコマンド集
基本コマンドに加えて、日々の開発で頻繁に使うコマンドをカテゴリ別にまとめました。手元にブックマークしておくと便利です。
起動・停止系
# 特定のサービスだけ起動(依存サービスも自動で起動される)
docker compose up -d app
# サービスを停止するがコンテナは削除しない(再開が速い)
docker compose stop
# 停止したサービスを再開
docker compose start
# 特定サービスだけ再起動
docker compose restart app
# イメージ・ボリューム・ネットワークもすべて削除(完全リセット)
docker compose down -v --rmi all --remove-orphansログ・デバッグ系
# 全サービスのログを表示(直近100行)
docker compose logs --tail=100
# 複数サービスのログをまとめて表示
docker compose logs -f app db
# コンテナ内でシェルを起動(対話的にデバッグ)
docker compose exec app sh
# 新しいコンテナを一時的に起動してコマンド実行(既存コンテナに影響なし)
docker compose run --rm app npm test
# コンテナのリソース使用量をリアルタイム監視
docker compose topビルド・イメージ系
# キャッシュを使わずにゼロからビルド
docker compose build --no-cache
# 特定サービスだけビルド
docker compose build app
# ビルド済みイメージの一覧を確認
docker compose images
# compose.ymlの設定内容を変数展開した状態で確認
docker compose config最後のdocker compose configは意外と見落とされがちですが、.envの変数が正しく展開されているかを確認するのに重宝します。「環境変数を設定したはずなのに反映されない」というときは、まずこのコマンドで実際の展開結果をチェックしてみてください。
runとexecの違い
似たようなコマンドとしてrunとexecがありますが、用途が異なります。
| コマンド | 動作 | 用途 |
|---|---|---|
docker compose exec | 稼働中のコンテナでコマンド実行 | DBへの接続、ログ確認など |
docker compose run | 新しいコンテナを作成してコマンド実行 | テスト実行、マイグレーションなど |
runは毎回新しいコンテナを作るため、--rmオプションを付けて使い捨てにするのが基本です。付け忘れると停止済みコンテナがどんどん溜まっていくので注意してください。
よくあるトラブルと対処法
初心者がつまずきやすいポイントをチェックリスト形式でまとめました。筆者自身もこれらの問題に一通り遭遇した経験があります。
- ポートの競合:
Bind for 0.0.0.0:5432 failed: port is already allocatedが出たら、ホスト側で同じポートを使うプロセスがないかlsof -i :5432で確認する - ボリュームのキャッシュ: コードを変更したのに反映されない場合、
docker compose up -d --buildでイメージを再ビルドする - 環境変数の未反映:
compose.ymlの環境変数を変更した後はdocker compose up -dで再作成が必要。restartでは反映されない - ネットワーク名の不一致: サービス間通信ではコンテナ名ではなく
compose.ymlのサービス名をホスト名として使う(例:db:5432) - Apple Siliconでのイメージ互換性: M1/M2 Macでは
platform: linux/amd64の指定が必要な場合がある
同じエラーに何度も悩まされている方は、このリストを手元に置いておくと原因の切り分けが早くなるのではないでしょうか。
トラブル対処の具体的な手順
上記のリストに加えて、筆者が実際に遭遇して対処に時間がかかったケースをもう少し掘り下げて紹介します。
「コンテナは起動しているのにアプリがつながらない」
docker compose psでコンテナがUpになっているのに、ブラウザからアクセスできないケースです。原因として多いのは以下の3つです。
- アプリが
localhostではなく0.0.0.0でリッスンしていない。コンテナ内のlocalhostはコンテナ自身を指すため、ホストマシンからはアクセスできません。Node.jsであればHOST=0.0.0.0の環境変数を追加してください。 - ポートマッピングの左右を逆に書いている。
"ホスト側:コンテナ側"の順番です。 - コンテナ内のアプリがまだ起動途中。
docker compose logs -f appでログを確認し、起動完了のメッセージが出ているかチェックします。
「docker compose upが途中で止まって返ってこない」
DockerfileのCMDやENTRYPOINTでフォアグラウンドプロセスが起動していない場合、コンテナが即座に終了し、restartポリシーによって起動→終了を繰り返すことがあります。docker compose logsでエラーを確認したうえで、Dockerfileの起動コマンドを見直してください。
「古いコンテナが残っていてサービス名が衝突する」
compose.ymlのプロジェクト名やサービス名を変更すると、古いコンテナが孤立して残ることがあります。以下のコマンドで孤立コンテナを含めて削除できます。
docker compose down --remove-orphans「ディスク容量が不足してビルドに失敗する」
Docker Composeを長期間使っていると、不要なイメージやボリューム、ビルドキャッシュが蓄積してディスクを圧迫することがあります。筆者も「No space left on device」エラーに初めて遭遇したときは少し焦りました。以下のコマンドで未使用のDockerリソースをまとめて整理できます。
# 未使用のイメージ・コンテナ・ネットワークを削除
docker system prune
# ボリュームも含めて削除(注意:未使用のボリュームデータも消える)
docker system prune --volumes
# 現在のディスク使用量を確認
docker system dfdocker system dfで現在の使用量を把握してから、必要に応じてpruneを実行するのが安全です。特にビルドキャッシュ(Build Cache)は意外と大きくなりがちなので、定期的に確認してみてください。
開発環境と本番環境の使い分け
Docker Composeは主に開発環境やCI環境での利用が推奨されていますが、設定ファイルの書き方を工夫することで、開発と本番の差異を最小限に管理できます。
複数のComposeファイルによるオーバーライド
Docker Composeは複数のYAMLファイルをマージする機能を持っています。ベースとなる設定と、環境ごとの差分を分離して管理できます。
# compose.yml(ベース設定)
services:
app:
build: .
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 3s
retries: 5# compose.override.yml(開発環境用。自動で読み込まれる)
services:
app:
ports:
- "3000:3000"
volumes:
- .:/app
- node_modules:/app/node_modules
environment:
NODE_ENV: development
db:
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
node_modules:# compose.prod.yml(本番環境用。明示的に指定して使う)
services:
app:
ports:
- "80:3000"
environment:
NODE_ENV: production
restart: always
db:
volumes:
- db_data:/var/lib/postgresql/data
restart: always
volumes:
db_data:compose.override.ymlはファイル名が決まっており、docker compose upを実行すると自動的にマージされます。本番環境用の設定を使いたい場合は、-fオプションで明示的に指定します。
# 開発環境(compose.yml + compose.override.yml が自動マージ)
docker compose up -d
# 本番環境(compose.yml + compose.prod.yml を明示指定)
docker compose -f compose.yml -f compose.prod.yml up -dこの仕組みのおかげで、開発環境ではソースコードのバインドマウントやデバッグ用ポートの公開を有効にしつつ、本番環境では不要な設定を排除するといった運用が自然に実現できます。
profilesによるサービスの選択的起動
開発時にだけ使いたいサービス(たとえばメール確認用のMailHogや、DB管理ツールのpgAdmin)がある場合、profiles機能が便利です。
services:
app:
build: .
ports:
- "3000:3000"
db:
image: postgres:16
pgadmin:
image: dpage/pgadmin4
ports:
- "8080:80"
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
profiles:
- debug
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025"
- "8025:8025"
profiles:
- debugprofilesが指定されたサービスは、通常のdocker compose upでは起動されません。使いたいときだけプロファイルを指定して起動します。
# 通常起動(app + db のみ)
docker compose up -d
# debugプロファイルを含めて起動(app + db + pgadmin + mailhog)
docker compose --profile debug up -d常時起動する必要のないツール類をprofilesで分離しておくと、普段の起動が軽くなりますし、compose.ymlもすっきりします。
Composeで構築する開発ワークフロー
ここまでの知識を組み合わせると、Composeを中心とした効率的な開発ワークフローが構築できます。筆者が実際のプロジェクトで運用しているフローを紹介します。
Makefileとの組み合わせ
docker composeコマンドはオプションが長くなりがちです。チームで頻繁に使うコマンドは、Makefileにまとめておくと入力の手間が省けます。
.PHONY: up down restart logs db-console fresh
# 開発環境を起動
up:
docker compose up -d
# 環境を停止
down:
docker compose down
# アプリだけ再ビルドして再起動
restart:
docker compose up -d --build app
# アプリのログを追従表示
logs:
docker compose logs -f app
# DBコンソールに接続
db-console:
docker compose exec db psql -U $${POSTGRES_USER} -d $${POSTGRES_DB}
# 環境を完全リセット(ボリューム削除→再ビルド→起動)
fresh:
docker compose down -v
docker compose up -d --buildmake up、make logs、make freshのように短いコマンドで操作できるようになります。新しいメンバーにも「とりあえずmake upで起動できる」と伝えれば済むため、ドキュメントの簡略化にもつながります。
筆者がこのアプローチを取り入れたのは、チーム内で「docker composeのあのオプション何だっけ」という質問が繰り返されたのがきっかけでした。Makefileに集約してからは、そうした問い合わせがほぼなくなりました。
watchモードによるホットリロード
Docker Compose V2.22以降では、watch機能が導入されました。ファイルの変更を検知して、自動的にコンテナへの同期や再ビルドを行ってくれます。
services:
app:
build: .
ports:
- "3000:3000"
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: ./package.jsondocker compose watchaction: syncはファイルの変更をコンテナに即座に同期し、action: rebuildは対象ファイルが変更されたときにイメージの再ビルドを自動で実行します。上記の例では、ソースコードの変更はリアルタイムに反映され、package.jsonが変わったときだけ再ビルドが走ります。
バインドマウントでも似たことはできますが、watchモードはファイルシステムの差異(特にmacOSとLinux間のパーミッションの違い)に強く、パフォーマンス面でも有利な場合があります。比較的新しい機能ではありますが、開発体験を向上させてくれるので、ぜひ試してみてください。
まとめ:小さく始めて段階的に学ぶ
Docker Composeは、compose.ymlの宣言的な記述と数個のコマンドだけで複数コンテナを一括管理できる強力なツールです。まずは本記事のようなシンプルな2サービス構成から始め、慣れてきたらRedisやNginxのリバースプロキシなどを追加していくのが効率的な学び方だと感じています。
本記事で紹介した内容を段階的に整理すると、以下のようなステップで学んでいくのがおすすめです。
- まずは2サービス構成:アプリ + DBのシンプルな構成でComposeの基本操作に慣れる
- 環境変数を.envに分離:機密情報の管理とチーム共有を見据えた運用を身につける
- 3サービス以上に拡張:Redis、Nginxなどを追加し、ネットワークやヘルスチェックの理解を深める
- オーバーライドファイルで環境分離:開発と本番の設定を適切に管理する
- Makefileやwatchモードで効率化:日々の開発ワークフローを洗練させる
一度Composeに慣れると、チームメンバー全員が同一の開発環境を即座に再現できるようになり、「自分の環境では動くのに」という問題も大幅に減ります。一概には言えない部分もありますが、開発生産性への投資効果は非常に高いツールです。
Docker Composeの導入やコンテナベースの開発環境構築でお困りのことがありましたら、こちらからお気軽にご相談ください。インフラ設計から運用まで、チームの状況に合わせたご提案が可能です。