mcpField
C
compose-patterns-2025

compose-patterns-2025

github.com/josiahsiegelクラウド・DevOps59176 回インストール19 週間前に更新
リポジトリ

compose-patterns-2025 について

本番向け Docker Compose 設定のリファレンススキルです。開発者が複数環境のデプロイを構成する際に役立つ、安全性、ヘルスチェック、ネットワーク、リソース、運用の指針をまとめています。

主な機能

  1. 01ベース設定と環境別上書き
  2. 02環境ごとの変数ファイル
  3. 03非 root 実行とシークレット管理
  4. 04ヘルスチェックと依存条件
  5. 05ネットワーク、ボリューム、リソース設定
  6. 06ログ、検証、トラブルシューティング

活用シーン

  1. 01開発、CI、本番の Compose 設定の分離
  2. 02コンテナのアクセスとシークレット管理のリスク低減
  3. 03サービスの準備状態と起動順序の設定
  4. 04デプロイ用 Compose ファイルの準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill compose-patterns-2025

インストール後、Claude Code で/compose-patterns-2025を入力して読み込まれたか確認します。

詳細

本番環境向け Docker Compose パターン(2025 年版)

概要

このスキルでは、公式の Docker ドキュメントと業界標準に基づき、2025 年に向けた本番環境対応の Docker Compose パターンとベストプラクティスを説明します。

ファイル形式の変更(2025 年)

重要: version フィールドは、Docker Compose v2.42 以降では廃止予定です。

正しい例(2025 年):

services:
  app:
    image: myapp:latest

誤った例(非推奨):

version: '3.8'  # DO NOT USE
services:
  app:
    image: myapp:latest

複数環境の運用方針

パターン:ベース設定と環境別のオーバーライド

compose.yaml(ベース設定):

services:
  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    environment:
      - NODE_ENV=production
    restart: unless-stopped

compose.override.yaml(開発環境 — 自動読み込み):

services:
  app:
    build:
      target: development
    volumes:
      - ./app/src:/app/src:cached
    environment:
      - NODE_ENV=development
      - DEBUG=*
    ports:
      - "9229:9229"  # Debugger

compose.prod.yaml(本番環境 — 明示的な指定が必要):

services:
  app:
    build:
      target: production
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '1'
          memory: 512M
      restart_policy:
        condition: on-failure
        max_attempts: 3

使用方法:

# Development (auto-loads compose.override.yaml)
docker compose up

# Production
docker compose -f compose.yaml -f compose.prod.yaml up -d

# CI/CD
docker compose -f compose.yaml -f compose.ci.yaml up --abort-on-container-exit

環境変数の管理

パターン:環境別の .env ファイル

.env.template(Git にコミット):

# Database
DB_HOST=sqlserver
DB_PORT=1433
DB_NAME=myapp
DB_USER=sa
# DB_PASSWORD= (set in actual .env)

# Redis
REDIS_HOST=redis
REDIS_PORT=6379
# REDIS_PASSWORD= (set in actual .env)

# Application
NODE_ENV=production
LOG_LEVEL=info

.env.dev:

DB_PASSWORD=Dev!Pass123
REDIS_PASSWORD=redis-dev-123
NODE_ENV=development
LOG_LEVEL=debug

.env.prod:

DB_PASSWORD=${PROD_DB_PASSWORD}  # From CI/CD
REDIS_PASSWORD=${PROD_REDIS_PASSWORD}
NODE_ENV=production
LOG_LEVEL=info

特定の環境を読み込む:

docker compose --env-file .env.dev up

セキュリティパターン

パターン:非 root ユーザーとして実行
services:
  app:
    image: node:20-alpine
    user: "1000:1000"  # UID:GID
    read_only: true
    tmpfs:
      - /tmp
      - /app/.cache
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Only if binding to ports < 1024
    security_opt:
      - no-new-privileges:true

Dockerfile でユーザーを作成:

FROM node:20-alpine

# Create app user
RUN addgroup -g 1000 appuser && \
    adduser -D -u 1000 -G appuser appuser

# Set ownership
WORKDIR /app
COPY --chown=appuser:appuser . .

USER appuser
パターン:シークレット管理

Docker Swarm のシークレット(本番環境):

services:
  app:
    secrets:
      - db_password
      - api_key

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    external: true  # Managed by Swarm

アプリケーションでシークレットにアクセス:

// Read from /run/secrets/
const fs = require('fs');
const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();

開発環境での代替方法(環境変数):

services:
  app:
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db_password

ヘルスチェックのパターン

パターン:包括的なヘルスチェック

HTTP エンドポイント:

services:
  web:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 40s

データベースへの ping:

services:
  postgres:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
      interval: 10s
      timeout: 3s
      retries: 3

カスタムスクリプト:

services:
  app:
    healthcheck:
      test: ["CMD", "node", "/app/scripts/healthcheck.js"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 40s

healthcheck.js:

const http = require('http');

const options = {
  hostname: 'localhost',
  port: 8080,
  path: '/health',
  timeout: 2000
};

const req = http.request(options, (res) => {
  process.exit(res.statusCode === 200 ? 0 : 1);
});

req.on('error', () => process.exit(1));
req.on('timeout', () => {
  req.destroy();
  process.exit(1);
});
req.end();

依存関係の管理

パターン:条件付きの順序付き起動
services:
  web:
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_started
      migration:
        condition: service_completed_successfully

  database:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 10s

  redis:
    # No health check needed, just wait for start

  migration:
    image: myapp:latest
    command: npm run migrate
    restart: "no"  # Run once
    depends_on:
      database:
        condition: service_healthy

ネットワーク分離のパターン

パターン:3 層ネットワークアーキテクチャ
services:
  nginx:
    image: nginx:alpine
    networks:
      - frontend
    ports:
      - "80:80"

  api:
    build: ./api
    networks:
      - frontend
      - backend

  database:
    image: postgres:16-alpine
    networks:
      - backend  # No frontend access

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # No external access
パターン:サービスごとのネットワーク
services:
  web-app:
    networks:
      - public
      - app-network

  api:
    networks:
      - app-network
      - data-network

  postgres:
    networks:
      - data-network

  redis:
    networks:
      - data-network

networks:
  public:
    driver: bridge
  app-network:
    driver: bridge
    internal: true
  data-network:
    driver: bridge
    internal: true

ボリュームのパターン

パターン:永続化のための名前付きボリューム
services:
  database:
    volumes:
      - db-data:/var/lib/postgresql/data  # Persistent data
      - ./init:/docker-entrypoint-initdb.d:ro  # Init scripts (read-only)
      - db-logs:/var/log/postgresql  # Logs

volumes:
  db-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /mnt/data/postgres  # Host path
  db-logs:
    driver: local
パターン:開発環境でのバインドマウント
services:
  app:
    volumes:
      - ./src:/app/src:cached  # macOS optimization
      - /app/node_modules  # Don't overwrite installed modules
      - app-cache:/app/.cache  # Named volume for cache

ボリュームマウントのオプション:

  • :ro - 読み取り専用
  • :rw - 読み書き可能(デフォルト)
  • :cached - macOS のパフォーマンス最適化(ホスト側を優先)
  • :delegated - macOS のパフォーマンス最適化(コンテナ側を優先)
  • :z - SELinux の単一コンテナ
  • :Z - SELinux の複数コンテナ

リソース管理のパターン

パターン:CPU とメモリの制限
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

合計リソースを計算:

# 3 app replicas + database + redis
services:
  app:
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '0.5'      # 3 x 0.5 = 1.5 CPUs
          memory: 512M     # 3 x 512M = 1.5GB

  database:
    deploy:
      resources:
        limits:
          cpus: '2'        # 2 CPUs
          memory: 4G       # 4GB

  redis:
    deploy:
      resources:
        limits:
          cpus: '0.5'      # 0.5 CPUs
          memory: 512M     # 512MB

# Total: 4 CPUs, 6GB RAM minimum

ロギングのパターン

パターン:一元化されたロギング
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
        labels: "app,environment"

代替方法:stdout/stderr にログを出力(12-factor):

services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

ログを表示:

docker compose logs -f app
docker compose logs --since 30m app
docker compose logs --tail 100 app

Init コンテナのパターン

パターン:データベースのマイグレーション
services:
  migration:
    image: myapp:latest
    command: npm run migrate
    depends_on:
      database:
        condition: service_healthy
    restart: "no"  # Run once
    networks:
      - backend

  app:
    image: myapp:latest
    depends_on:
      migration:
        condition: service_completed_successfully
    networks:
      - backend

YAML アンカーとエイリアス

パターン:再利用可能な設定
x-common-app-config: &common-app
  restart: unless-stopped
  logging:
    driver: "json-file"
    options:
      max-size: "10m"
      max-file: "3"
  security_opt:
    - no-new-privileges:true
  cap_drop:
    - ALL
  cap_add:
    - NET_BIND_SERVICE

services:
  app1:
    <<: *common-app
    build: ./app1
    ports:
      - "8001:8080"

  app2:
    <<: *common-app
    build: ./app2
    ports:
      - "8002:8080"

  app3:
    <<: *common-app
    build: ./app3
    ports:
      - "8003:8080"
パターン:環境別のオーバーライド
x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

x-resources: &default-resources
  limits:
    cpus: '1'
    memory: 512M
  reservations:
    cpus: '0.5'
    memory: 256M

services:
  app:
    logging: *default-logging
    deploy:
      resources: *default-resources

ポートバインドのパターン

パターン:セキュリティ優先のポートバインド
services:
  # Public services
  web:
    ports:
      - "80:8080"
      - "443:8443"

  # Development only (localhost binding)
  debug:
    ports:
      - "127.0.0.1:9229:9229"  # Debugger only accessible from host

  # Environment-based binding
  app:
    ports:
      - "${DOCKER_WEB_PORT_FORWARD:-127.0.0.1:8000}:8000"

環境による制御:

# Development (.env.dev)
DOCKER_WEB_PORT_FORWARD=127.0.0.1:8000  # Localhost only

# Production (.env.prod)
DOCKER_WEB_PORT_FORWARD=8000  # All interfaces

再起動ポリシーのパターン

services:
  # Always restart (production services)
  app:
    restart: always

  # Restart unless manually stopped (most common)
  database:
    restart: unless-stopped

  # Never restart (one-time tasks)
  migration:
    restart: "no"

  # Restart on failure only (with Swarm)
  worker:
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s

検証とテスト

パターン:デプロイ前の検証
#!/bin/bash
set -euo pipefail

echo "Validating Compose syntax..."
docker compose config > /dev/null

echo "Building images..."
docker compose build

echo "Running security scan..."
for service in $(docker compose config --services); do
  image=$(docker compose config | yq ".services.$service.image")
  if [ -n "$image" ]; then
    docker scout cves "$image" || true
  fi
done

echo "Starting services..."
docker compose up -d

echo "Checking health..."
sleep 10
docker compose ps

echo "Running smoke tests..."
curl -f http://localhost:8080/health || exit 1

echo "✓ All checks passed"

本番環境の完全な例

# Modern Compose format (no version field for v2.40+)

x-common-service: &common-service
  restart: unless-stopped
  logging:
    driver: "json-file"
    options:
      max-size: "10m"
      max-file: "3"
  security_opt:
    - no-new-privileges:true

services:
  nginx:
    <<: *common-service
    image: nginxinc/nginx-unprivileged:alpine
    ports:
      - "80:8080"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
    networks:
      - frontend
    depends_on:
      api:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
      interval: 30s

  api:
    <<: *common-service
    build:
      context: ./api
      dockerfile: Dockerfile
      target: production
    user: "1000:1000"
    read_only: true
    tmpfs:
      - /tmp
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    networks:
      - frontend
      - backend
    depends_on:
      migration:
        condition: service_completed_successfully
      redis:
        condition: service_started
    env_file:
      - .env
    healthcheck:
      test: ["CMD", "node", "healthcheck.js"]
      interval: 30s
      start_period: 40s
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M

  migration:
    image: myapp:latest
    command: npm run migrate
    restart: "no"
    networks:
      - backend
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    <<: *common-service
    image: postgres:16-alpine
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
    secrets:
      - postgres_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 10s
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 2G

  redis:
    <<: *common-service
    image: redis:7.4-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    networks:
      - backend
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s

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

volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local

secrets:
  postgres_password:
    file: ./secrets/postgres_password.txt

避けるべきよくある間違い

  1. version フィールドを使用する - 2025 年には廃止予定です
  2. ヘルスチェックがない - 競合状態につながります
  3. root として実行する - セキュリティリスクになります
  4. リソース制限がない - ホストのリソースを使い果たす可能性があります
  5. シークレットをハードコードする - シークレットまたは環境変数を使用してください
  6. ロギングの制限がない - ディスク容量の問題につながります
  7. 本番環境でバインドマウントを使用する - 名前付きボリュームを使用してください
  8. 再起動ポリシーがない - サービスが復旧しません
  9. ネットワークを分離していない - すべてのサービスが相互に通信できます
  10. .dockerignore を使用していない - ビルドコンテキストが大きくなります

トラブルシューティング用コマンド

# Validate syntax
docker compose config

# View merged configuration
docker compose config --services

# Check which file is being used
docker compose config --files

# View environment interpolation
docker compose config --no-interpolate

# Check service dependencies
docker compose config | yq '.services.*.depends_on'

# View resource usage
docker stats $(docker compose ps -q)

# Debug startup issues
docker compose up --no-deps service-name

# Force recreate
docker compose up --force-recreate service-name

参考資料

コメント