現実の問題:docker-compose.ymlがどんどん肥大化していく
約6ヶ月前、自分は8つのマイクロサービスで構成されたECサイトのプロジェクトをメンテナンスしていた。当時のdocker-compose.ymlはそれなりにスッキリしていた——チームがobservabilityのためにPrometheus・Grafana・Jaegerを追加し、メールテスト用にMailhog、DB管理用にpgAdminを追加するまでは。
結果は? docker-composeファイルが300行に膨れ上がった。stagingにデプロイするたびに、不要なサービスを手作業でコメントアウトしなければならなかった。一度コメントを戻し忘れたことがあり、Grafanaが本番環境で起動していたのに誰も気づかなかった。不要なmonitoringツールが動いていただけで、サーバーのRAMが40%も増加した。
さらに、コンテナのメモリリークを発見したときは丸2日間デバッグに費やした。原因の一つは、ローカルとstagingの環境が一致していなかったこと——ローカルではJaegerトレーシングが動いていたが、stagingにはなかった。トレースログが合わず、ずっと間違った方向を追い続けてしまった。
そのときに掘り起こしたのがDocker Compose Profiles——Compose v1.28から存在していたのに、あまり注目されていない機能だ。
根本原因:何でも一つのファイルに詰め込む設計が間違い
私たちは、まったく異なる四つのシナリオに対して一つのdocker-compose.ymlファイルを使い回そうとしていた:
- ローカル開発:ホットリロード、デバッグポート、メールキャッチャー、DB管理UIが必要
- CI/CDテスト:アプリ+データベースだけあればよく、テスト終了後は削除
- Staging:本番に近い構成に、monitoringを追加
- 本番(Production):本物のサービスのみ、余計なものは一切不要
多くの人は複数ファイルで解決しようとする:docker-compose.override.yml、docker-compose.prod.yml…この方法も悪くはない。ただ、サービス数が増えると混乱してくる——マージの順序を覚えておかなければならず、設定の競合が起きやすい。
試してきた解決策
方法1:複数のComposeファイルを手動でマージ
# 複数ファイルを指定して実行
docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d
# またはoverrideを使う
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
動くことは動くが、疲れる。開発者全員が長いコマンドを覚えなければならない。CI/CDスクリプトにハードコードする羽目になる。新しい環境を追加するたびにファイルとスクリプトが増え続ける。
方法2:環境変数でサービスのON/OFFを切り替える
環境変数とextendsを使ったトリックを使う人もいるが、Composeは条件によるサービスの有効化・無効化をネイティブにはサポートしていない。結果はハックだらけのコードになり、チームに新しく入った人には読み解けない。
最もシンプルな解決策:Docker Compose Profiles
Profilesはまさにこの問題のために設計された公式機能だ。各サービスに一つまたは複数のプロファイルを割り当てる。実行時に有効化するプロファイルを宣言すると、そのプロファイルに属するサービス(またはプロファイルなしのサービス)だけが起動する。
Docker Compose Profilesの実践的な使い方
基本構造
# docker-compose.yml
version: '3.9'
services:
# メインサービス — プロファイルなし = 常に起動
app:
image: myapp:latest
ports:
- "3000:3000"
environment:
- NODE_ENV=production
postgres:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
# --- "debug" プロファイルが有効なときだけ起動 ---
mailhog:
image: mailhog/mailhog
profiles: ["debug"]
ports:
- "8025:8025"
pgadmin:
image: dpage/pgadmin4
profiles: ["debug"]
ports:
- "5050:80"
environment:
- [email protected]
- PGADMIN_DEFAULT_PASSWORD=admin
# --- "monitoring" プロファイルが有効なときだけ起動 ---
prometheus:
image: prom/prometheus
profiles: ["monitoring"]
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana
profiles: ["monitoring"]
ports:
- "3001:3000"
# --- CIテスト専用 ---
test-runner:
image: myapp:test
profiles: ["test"]
command: npm test
depends_on:
- postgres
- redis
volumes:
pgdata:
環境ごとの起動方法
# 本番:app + postgres + redisのみ
docker compose up -d
# ローカル開発:mailhog + pgadminも追加
docker compose --profile debug up -d
# stagingにmonitoringを追加:
docker compose --profile monitoring up -d
# debugとmonitoringを同時に有効化:
docker compose --profile debug --profile monitoring up -d
# CI/CDでテスト実行:
docker compose --profile test run --rm test-runner
--profileフラグのほかに、環境変数で指定する方法もある——.envファイルと組み合わせると便利だ:
# 環境変数 COMPOSE_PROFILES を使う
export COMPOSE_PROFILES=debug,monitoring
docker compose up -d
# または .env ファイルに記述
echo "COMPOSE_PROFILES=debug" >> .env
一つのサービスを複数のプロファイルに所属させる
jaeger:
image: jaegertracing/all-in-one
# debugまたはmonitoringを有効にしたときに起動
profiles: ["debug", "monitoring"]
ports:
- "16686:16686"
- "4317:4317"
どのサービスがどのプロファイルに属するか確認する
# すべてのサービスとそのプロファイルを表示
docker compose config --services
# 展開済みの設定全体を表示
docker compose config
# "debug" プロファイルを有効にしたときに起動するサービスを一覧表示
docker compose --profile debug config --services
ECサイトのプロジェクトで実際に使っている構成
version: '3.9'
services:
api:
build: ./api
environment:
- DATABASE_URL=postgresql://user:pass@postgres:5432/ecom
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis
postgres:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_DB=ecom
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
redis:
image: redis:7-alpine
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
# === デバッグツール ===
pgadmin:
image: dpage/pgadmin4
profiles: ["debug"]
ports:
- "5050:80"
mailhog:
image: mailhog/mailhog
profiles: ["debug"]
ports:
- "8025:8025"
# デバッグモードを有効にしたapi
api-dev:
build:
context: ./api
target: development
profiles: ["debug"]
volumes:
- ./api:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DEBUG=*
ports:
- "9229:9229" # Node.js デバッグポート
# === モニタリング ===
prometheus:
image: prom/prometheus
profiles: ["monitoring"]
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
profiles: ["monitoring"]
ports:
- "3001:3000"
volumes:
- grafana_data:/var/lib/grafana
cadvisor:
image: gcr.io/cadvisor/cadvisor
profiles: ["monitoring"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
volumes:
pgdata:
grafana_data:
実際のワークフローへの組み込み
環境ごとの .env ファイル
# .env.local
COMPOSE_PROFILES=debug
NODE_ENV=development
# .env.staging
COMPOSE_PROFILES=monitoring
NODE_ENV=staging
# .env.production
COMPOSE_PROFILES=
NODE_ENV=production
# 環境ごとにデプロイ
docker compose --env-file .env.local up -d
docker compose --env-file .env.staging up -d
docker compose --env-file .env.production up -d
Makefileでコマンドをシンプルにする
up-dev:
docker compose --profile debug up -d
up-staging:
docker compose --profile monitoring up -d
up-prod:
docker compose up -d
test:
docker compose --profile test run --rm test-runner
down:
docker compose --profile debug --profile monitoring --profile test down
見落としがちな落とし穴:プロファイルを指定せずにdocker compose downを実行すると、Composeはプロファイルを持たないサービスしか停止しない。すべてを停止したい場合は、プロファイルをすべて列挙するか、Compose v2.20+の--profiles "*"フラグを使う:
# プロファイルを持つサービスも含めてすべて停止
docker compose --profiles "*" down
6ヶ月間の実運用で得られた成果
Profilesに移行してから、docker-compose.ymlは明らかにスッキリした——コメントブロックが散乱することもなくなった。チームの開発者が誤ってmonitoringツールを本番環境で起動するミスもなくなった。COMPOSE_PROFILESを設定しなければ、そのサービスはComposeの世界に存在しないも同然だからだ。
冒頭で話したメモリリークの件も、当時Profilesを使っていたら、Jaegerはローカルとstagingで同じdebugプロファイルとして均一に動いていたはずだ。トレースログも一致し、2日間も間違った方向を追い続けることはなかっただろう。
Profilesがすべての問題を解決するわけではない——シークレット管理や環境ごとの設定は別のツールが必要だ。ただ、3人以上のチームで複数の環境を運用しているなら、Docker Composeの中で自分が使ったことのある中で最も明快な整理方法だ。一度試すと、元の方法には戻れなくなる。

