Docker Compose Profiles:環境ごとにオプションサービスを管理する — debug・monitoringを完全に分離

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

現実の問題: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.ymldocker-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の中で自分が使ったことのある中で最も明快な整理方法だ。一度試すと、元の方法には戻れなくなる。

Share: