Featured image of post 本番向けDocker Compose:デプロイのベストプラクティス Featured image of post 本番向けDocker Compose:デプロイのベストプラクティス

本番向けDocker Compose:デプロイのベストプラクティス

本番環境でのDocker Compose活用術。Composeファイル管理、ヘルスチェック、ログドライバ、リソース制限、シークレット管理、ローリングアップデート、リバースプロキシ統合まで解説。

はじめに

Docker Composeはローカル開発環境のツールとして知られていますが、小中規模アプリケーションのシングルホスト本番デプロイでもその利用が増えています。「Composeは本番向けではない」という批判に対して、筆者はこう答えます——適切に設定されたVM上のComposeスタックは、単純さと信頼性の適切なバランスを提供すると。

本記事では、ComposeファイルをInfrastructure as Codeとして扱い、バージョン管理、CI/CD統合、本番固有の堅牢化を実現するための実践的ガイドラインを紹介します。


1. Composeファイル構成とバージョン管理

Compose Specification(v3.8+)を使用し、マルチファイル戦略で関心を分離します。

compose.yml           # 全環境共通のベース設定
compose.override.yml  # ローカル開発用(本番では未使用)
compose.prod.yml      # 本番固有の設定

YAMLアンカーを使って各サービス間の設定重複を削減します。

x-logging: &logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  app:
    image: myapp:latest
    logging: *logging
    restart: unless-stopped

--env-file フラグで環境ごとに変数を切り替え、シークレットのハードコーディングを避けます。非推奨の docker-compose(v1)ではなく docker compose(v2)を使用しましょう。


2. サービス依存関係とヘルスチェック

depends_on だけでは本番環境で不十分です。condition: service_healthy を使い、依存サービスがヘルスチェックに合格してから起動するよう設定します。

services:
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 30s

  app:
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
      interval: 10s
      timeout: 3s
      retries: 3

Nginx → App → Redis → PostgreSQL のカスケード型ヘルスチェックにより、各サービスは依存先の準備が整ってから起動します。


3. ネットワーク設定

driver: bridge のカスタムネットワークを作成してサービスを分離します。デフォルトの bridge ネットワークにはサービスディスカバリがなく、本番環境では不適切です。

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true
  monitoring:
    driver: bridge

services:
  app:
    networks:
      frontend:
      backend:
      monitoring:

frontend(リバースプロキシ+アプリ)、backend(internal、アプリ+DB)、monitoring(可観測性ツール)の3ネットワーク構成でクリーンな分離を実現します。ネットワークエイリアスを使うと再起動後も安定したDNS解決が可能です。


4. リソース制約と制限

本番コンテナにはリソースの上限と下限を設定し、単一サービスの暴走によるホスト全体の停止を防ぎます。

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
          pids: 100
        reservations:
          cpus: '0.25'
          memory: 128M
ディレクティブ目的
limits.cpusCPUコア数の上限(例:'0.5' = 半コア)
limits.memoryOOM Killerが発動するメモリ上限
reservations保証される最低リソース
ulimitsファイルディスクリプタ制限(例:nofile: 65536

Node.jsの場合は --max-old-space-size をメモリ制限に合わせ、ガベージコレクションのスラッシングを防止します。


5. ロギングと可観測性

トラブルシューティングには集中ロギングが不可欠です。json-file ドライバにローテーション設定を追加し、ディスク枯渇を防ぎます。

x-logging: &logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "5"

本番環境で推奨するログドライバ:

ドライバ転送先ユースケース
fluentdFluentd → Elasticsearch全文検索と分析
gelfGraylog構造化ログ管理
awslogsAmazon CloudWatchAWS環境

アプリケーションは構造化JSONでログを出力すると、Dockerのログチェーンと親和性が高まります。docker compose logs --tail 100 --follow でリアルタイムに確認できます。


6. シークレットと環境管理

Composeファイルにシークレットをハードコードしてはいけません。SwarmモードではDocker Secretsを使用し、ファイルとしてマウントします。

secrets:
  db_password:
    file: ./secrets/db_password.txt

services:
  app:
    secrets:
      - db_password

非Swarm環境では .env ファイル(.gitignore に追加)を sopsage で暗号化して管理します。Composeファイル内では $ 構文で参照します。

services:
  app:
    environment:
      - DATABASE_URL=postgres://user:${DB_PASSWORD}@postgres:5432/myapp

シークレットは定期的にローテーションし、SERVICE_NAME_SECRET のような命名規則で管理します。


7. ローリングアップデートとゼロダウンタイムデプロイ

deploy.update_config でヘルスチェックゲーティング付きのローリング更新を設定します。

services:
  app:
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
      rollback_config:
        parallelism: 1
        order: stop-first
パラメータ説明
parallelism同時に更新するコンテナ数
delay更新グループ間の待機時間
orderstart-first(ブルーグリーン)または stop-first
failure_actionpausecontinuerollback

docker compose up --detach --wait でヘルスチェックの合格を待ってからデプロイ完了とみなす、安全な更新が可能です。


8. リバースプロキシ統合

TraefikはComposeデプロイに最適なリバースプロキシです。Dockerラベルで動的にサービスを検出し、Let’s EncryptでSSL証明書を自動管理します。

services:
  traefik:
    image: traefik:v3
    command:
      - "--providers.docker=true"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.tlschallenge=true"
    ports:
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

  app:
    labels:
      - "traefik.http.routers.app.rule=Host(`example.com`)"
      - "traefik.http.services.app.loadbalancer.server.port=3000"

Nginxは手動でのupstream定義とSSL終端設定が必要です。CaddyはTraefikと同様の自動HTTPSを提供しますが、動的再設定の柔軟性は劣ります。


9. 監視とバックアップ

監視スタックは独立した monitoring.yml で定義し、アプリケーションと関心を分離します。cAdvisor + Prometheus + Grafanaの組み合わせが定番です。

データベースのバックアップはcronで定期的に docker exec ダンプを実行します。

0 3 * * * docker exec postgres pg_dump -U user mydb > /backups/db_$(date +\%Y\%m\%d).sql

本番サービスには restart: unless-stopped を使用します。restart: always と異なり、手動で docker stop した後に再起動しないため、メンテナンス時の意図しない再起動を防げます。


まとめ

Docker Composeは適切な規模のデプロイメントにおいて、本番利用に十分耐えうるツールです。Composeファイルをコードとして扱い、カスケード型ヘルスチェックを設計し、リソース制限を設定し、ログを集中管理し、シークレットを適切に扱い、ローリング更新を実装する——これらのプラクティスにより、Composeは開発の便利ツールから堅牢なデプロイ基盤へと進化します。

シングルホストの限界を超える場合も、同じComposeファイルをDocker SwarmやKubernetesに移行する足がかりとして活用できます。