Docker Compose 応用 (Compose Advanced)
プロファイル、depends_on の高度な制御、healthcheck、環境変数の管理パターンなど、Docker Compose の応用機能を活用してプロダクション品質の構成を構築する。
Docker Compose 応用 (Compose Advanced)
プロファイル、depends_on の高度な制御、healthcheck、環境変数の管理パターンなど、Docker Compose の応用機能を活用してプロダクション品質の構成を構築する。
この章で学ぶこと
- プロファイルによるサービスの選択的起動 -- 開発・テスト・監視など、用途に応じたサービスのグルーピングと選択的起動を実装する
- depends_on と healthcheck の高度な制御 -- サービス間の依存関係を精密に管理し、確実な起動順序を保証する
- 環境変数と設定の管理パターン -- 複数環境での設定切り替え、シークレット管理、ファイルのオーバーライドを実践する
- YAML アンカーと Extension Fields の活用 -- 設定の DRY 化と保守性の向上を実現する
- リソース制限・ロギング・セキュリティ設定 -- プロダクション品質の Compose 構成を構築する
前提知識
このガイドを読む前に、以下の知識があると理解が深まります:
- 基本的なプログラミングの知識
- 関連する基礎概念の理解
- Docker Compose 基礎 (Compose Basics) の内容を理解していること
1. プロファイル (Profiles)
1.1 プロファイルの概要
Docker Compose のプロファイル機能は、サービスを論理的にグルーピングし、必要に応じて選択的に起動する仕組みである。開発ツール、テストランナー、監視スタック、デバッグ用ツールなど、常時稼働が不要なサービスを管理するのに最適である。
プロファイルが指定されていないサービスは「デフォルト」として常に起動される。プロファイルが指定されたサービスは、明示的にそのプロファイルを有効化しない限り起動されない。
+------------------------------------------------------------------+
| プロファイルによるサービスのグルーピング |
+------------------------------------------------------------------+
| |
| [デフォルト] (プロファイルなし → 常に起動) |
| app, db, redis |
| |
| [debug プロファイル] (--profile debug で起動) |
| pgadmin, redis-commander |
| |
| [monitoring プロファイル] (--profile monitoring で起動) |
| prometheus, grafana, alertmanager |
| |
| [test プロファイル] (--profile test で起動) |
| test-runner, db-test, test-mail |
| |
| [seed プロファイル] (--profile seed で起動) |
| db-seeder, sample-data-loader |
| |
+------------------------------------------------------------------+
1.2 プロファイルの設定
# docker-compose.yml
services:
# プロファイルなし → 常に起動
app:
build: .
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
# debug プロファイル
pgadmin:
image: dpage/pgadmin4:latest
profiles: ["debug"]
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
redis-commander:
image: rediscommander/redis-commander:latest
profiles: ["debug"]
environment:
REDIS_HOSTS: local:redis:6379
ports:
- "8081:8081"
# monitoring プロファイル
prometheus:
image: prom/prometheus:latest
profiles: ["monitoring"]
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
profiles: ["monitoring"]
ports:
- "3001:3000"
volumes:
- grafana_data:/var/lib/grafana
alertmanager:
image: prom/alertmanager:latest
profiles: ["monitoring"]
volumes:
- ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml
ports:
- "9093:9093"
# test プロファイル
test-runner:
build:
context: .
target: test
profiles: ["test"]
depends_on:
db:
condition: service_healthy
command: npm test
# seed プロファイル (初期データ投入)
db-seeder:
build:
context: .
dockerfile: Dockerfile.seed
profiles: ["seed"]
depends_on:
db:
condition: service_healthy
command: npx prisma db seed
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
volumes:
grafana_data:1.3 プロファイルの起動コマンド
# デフォルトサービスのみ起動
docker compose up -d
# デバッグツールを追加起動
docker compose --profile debug up -d
# 複数プロファイル同時
docker compose --profile debug --profile monitoring up -d
# テスト実行
docker compose --profile test run --rm test-runner
# 環境変数で指定
COMPOSE_PROFILES=debug,monitoring docker compose up -d
# プロファイル指定のサービスのみ停止
docker compose --profile debug stop
# 特定プロファイルのサービス一覧を確認
docker compose --profile test ps
# 全プロファイルを含む全サービスの状態確認
docker compose --profile "*" ps1.4 プロファイルの実践的な活用パターン
パターン A: 開発/ステージング/本番の切り替え
services:
app:
build: .
ports:
- "3000:3000"
# 開発専用のメールキャッチャー
mailhog:
image: mailhog/mailhog:latest
profiles: ["dev"]
ports:
- "1025:1025"
- "8025:8025" # Web UI
# ステージング用の負荷テストツール
k6:
image: grafana/k6:latest
profiles: ["staging"]
volumes:
- ./tests/load:/scripts
command: run /scripts/load-test.js
# 本番用のログ収集
fluentd:
image: fluent/fluentd:v1.16
profiles: ["production"]
volumes:
- ./fluentd/conf:/fluentd/etc
ports:
- "24224:24224"パターン B: データベースマイグレーション管理
services:
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
# マイグレーション実行
migrate:
build: .
profiles: ["migrate"]
depends_on:
db:
condition: service_healthy
command: npx prisma migrate deploy
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
# マイグレーション生成(開発時のみ)
migrate-dev:
build: .
profiles: ["migrate-dev"]
depends_on:
db:
condition: service_healthy
command: npx prisma migrate dev
volumes:
- ./prisma:/app/prisma
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
# DB スキーマのリセット(危険操作)
db-reset:
build: .
profiles: ["db-reset"]
depends_on:
db:
condition: service_healthy
command: npx prisma migrate reset --force
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp2. depends_on と healthcheck
2.1 depends_on の 3 つの条件
Docker Compose では、サービス間の依存関係を 3 つの条件で制御できる。これにより、単純な起動順序の制御から、ヘルスチェックの通過やワンショットタスクの完了待ちまで、柔軟な制御が可能になる。
services:
app:
depends_on:
db:
condition: service_healthy # ヘルスチェックが通るまで待機
redis:
condition: service_started # コンテナが起動したら OK
migration:
condition: service_completed_successfully # 正常終了まで待機
restart: true # 再起動時も待機各条件の詳細な動作は以下の通りである。
| 条件 | 動作 | 典型的な用途 |
|---|---|---|
service_started |
コンテナのプロセスが起動したら即座に次へ進む | 起動が速いサービス(Redis 等) |
service_healthy |
healthcheck が passing になるまで待機する | DB、Elasticsearch 等の初期化に時間がかかるサービス |
service_completed_successfully |
コンテナが終了コード 0 で完了するまで待機する | マイグレーション、シード、初期化スクリプト |
2.2 healthcheck の詳細設定
各種データストア・サービスに対する healthcheck の実装例を示す。ヘルスチェックは、サービスが「起動した」だけでなく「リクエストを受け付けられる状態になった」ことを確認するために不可欠である。
services:
# PostgreSQL
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 5s # チェック間隔
timeout: 5s # タイムアウト
retries: 5 # 失敗許容回数
start_period: 30s # 起動猶予時間 (この間の失敗はカウントしない)
# MySQL
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
# MariaDB
mariadb:
image: mariadb:11
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
# Redis
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
# Redis (パスワード付き)
redis-auth:
image: redis:7-alpine
command: redis-server --requirepass mypassword
healthcheck:
test: ["CMD", "redis-cli", "-a", "mypassword", "ping"]
interval: 5s
timeout: 3s
retries: 5
# MongoDB
mongodb:
image: mongo:7
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# HTTP サービス
api:
build: .
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
# HTTP サービス (wget を使う場合 - curl がないイメージ向け)
api-alpine:
build: .
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1"]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
# Elasticsearch
elasticsearch:
image: elasticsearch:8.12.0
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\"\\|\"status\":\"yellow\"'"]
interval: 10s
timeout: 10s
retries: 10
start_period: 60s
# RabbitMQ
rabbitmq:
image: rabbitmq:3-management-alpine
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 10s
retries: 5
start_period: 30s
# Kafka (KRaft mode)
kafka:
image: bitnami/kafka:3.7
healthcheck:
test: ["CMD-SHELL", "kafka-broker-api-versions.sh --bootstrap-server localhost:9092 > /dev/null 2>&1"]
interval: 10s
timeout: 10s
retries: 10
start_period: 60s
# MinIO (S3互換ストレージ)
minio:
image: minio/minio:latest
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s2.3 依存関係の可視化
+------------------------------------------------------------------+
| サービス依存関係グラフ |
+------------------------------------------------------------------+
| |
| migration ──(completed)──> db ──(healthy)──+ |
| ^ | |
| | v |
| seed ──(completed)──────────+ app ──> redis |
| | (started) |
| v |
| worker ──> redis |
| (started) |
| |
+------------------------------------------------------------------+
2.4 複雑な依存関係チェーンの実装
実際のアプリケーションでは、DB 起動 → マイグレーション → シードデータ投入 → アプリ起動という一連の流れが必要になる。以下はその完全な実装例である。
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
volumes:
- pgdata:/var/lib/postgresql/data
# ステップ 1: マイグレーション実行
migration:
build:
context: .
target: migration
depends_on:
db:
condition: service_healthy
command: npx prisma migrate deploy
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
# ステップ 2: シードデータ投入 (マイグレーション完了後)
seed:
build:
context: .
target: seed
depends_on:
migration:
condition: service_completed_successfully
command: npx prisma db seed
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
# ステップ 3: アプリ起動 (シード完了後)
app:
build:
context: .
target: production
depends_on:
seed:
condition: service_completed_successfully
redis:
condition: service_healthy
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
REDIS_URL: redis://redis:6379
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
# ワーカープロセス (アプリと同じ依存関係)
worker:
build:
context: .
target: production
depends_on:
seed:
condition: service_completed_successfully
redis:
condition: service_healthy
command: node dist/worker.js
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
REDIS_URL: redis://redis:6379
volumes:
pgdata:2.5 ヘルスチェックのカスタムスクリプト
複雑なヘルスチェックが必要な場合は、専用のスクリプトを用意してコンテナにコピーする。
#!/bin/bash
# healthcheck.sh - 複合的なヘルスチェック
# 1. HTTP エンドポイントの確認
if ! curl -sf http://localhost:3000/health > /dev/null 2>&1; then
echo "HTTP health check failed"
exit 1
fi
# 2. DB 接続の確認
if ! node -e "
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
prisma.\$queryRaw\`SELECT 1\`.then(() => process.exit(0)).catch(() => process.exit(1));
" 2>/dev/null; then
echo "Database connection check failed"
exit 1
fi
# 3. Redis 接続の確認
if ! node -e "
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
redis.ping().then(() => process.exit(0)).catch(() => process.exit(1));
" 2>/dev/null; then
echo "Redis connection check failed"
exit 1
fi
echo "All health checks passed"
exit 0# Dockerfile
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
COPY healthcheck.sh /usr/local/bin/healthcheck.sh
RUN chmod +x /usr/local/bin/healthcheck.sh
HEALTHCHECK --interval=15s --timeout=10s --retries=3 --start-period=30s \
CMD /usr/local/bin/healthcheck.sh3. 環境変数の管理
3.1 環境変数の優先順位
Docker Compose では、環境変数の値が複数のソースから供給される場合、明確な優先順位が定められている。
+------------------------------------------------------------------+
| 環境変数の優先順位 (上が最優先) |
+------------------------------------------------------------------+
| |
| 1. docker compose run -e VAR=value (CLI 直接指定) |
| 2. environment: セクション |
| 3. --env-file で指定したファイル |
| 4. env_file: セクション |
| 5. Dockerfile の ENV |
| 6. シェルの環境変数 (.env ファイル経由) |
| |
+------------------------------------------------------------------+
3.2 .env ファイルの使い分け
# .env (Compose 変数の展開用。docker compose が自動読み込み)
COMPOSE_PROJECT_NAME=myapp
POSTGRES_VERSION=16
NODE_VERSION=20
APP_PORT=3000
# .env.development (アプリ用。env_file で明示的に読み込む)
NODE_ENV=development
DATABASE_URL=postgresql://postgres:postgres@db:5432/myapp_dev
REDIS_URL=redis://redis:6379
LOG_LEVEL=debug
CORS_ORIGIN=http://localhost:3000
SESSION_SECRET=dev-secret-key-not-for-production
SMTP_HOST=mailhog
SMTP_PORT=1025
# .env.staging (ステージング用)
NODE_ENV=staging
DATABASE_URL=postgresql://staging_user:staging_pass@db:5432/myapp_staging
REDIS_URL=redis://redis:6379
LOG_LEVEL=info
CORS_ORIGIN=https://staging.example.com
# .env.production (本番用)
NODE_ENV=production
DATABASE_URL=postgresql://user:password@db-prod:5432/myapp
LOG_LEVEL=warn
CORS_ORIGIN=https://www.example.com# docker-compose.yml
services:
app:
image: node:${NODE_VERSION}-alpine # .env の変数を使用
env_file:
- .env.development # アプリ用環境変数
environment:
# env_file の値を上書き
LOG_LEVEL: ${LOG_LEVEL:-info} # デフォルト値付き
db:
image: postgres:${POSTGRES_VERSION}-alpine3.3 環境変数の展開構文
services:
app:
environment:
# 基本形
DB_HOST: ${DB_HOST}
# デフォルト値 (未設定 or 空文字の場合)
DB_PORT: ${DB_PORT:-5432}
# デフォルト値 (未定義の場合のみ)
DB_NAME: ${DB_NAME-myapp}
# 未設定時にエラー
DB_PASSWORD: ${DB_PASSWORD:?Database password must be set}
# 設定済みの場合に代替値を使用
DB_SSL: ${DB_HOST:+true}
# ネストした変数展開(Compose V2.24+)
FULL_DB_URL: "postgresql://${DB_USER:-postgres}:${DB_PASSWORD}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-myapp}"3.4 シークレット管理
Docker Compose のシークレット機能は、パスワードや API キーなどの機密情報を環境変数に直接書かずに管理する方法を提供する。
# docker-compose.yml
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
app:
build: .
secrets:
- db_password
- api_key
- jwt_secret
environment:
# アプリケーション側でシークレットファイルを読む
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
JWT_SECRET_FILE: /run/secrets/jwt_secret
secrets:
db_password:
file: ./secrets/db_password.txt # ファイルから読み込み
api_key:
environment: API_KEY # 環境変数から (Compose V2.22+)
jwt_secret:
file: ./secrets/jwt_secret.txtアプリケーション側でシークレットファイルを読む実装例(Node.js):
// config/secrets.js
const fs = require('fs');
const path = require('path');
function readSecret(name) {
const filePath = process.env[`${name}_FILE`];
if (filePath && fs.existsSync(filePath)) {
return fs.readFileSync(filePath, 'utf8').trim();
}
// フォールバック: 環境変数から直接取得
return process.env[name];
}
module.exports = {
dbPassword: readSecret('DB_PASSWORD'),
apiKey: readSecret('API_KEY'),
jwtSecret: readSecret('JWT_SECRET'),
};3.5 .env ファイルの .gitignore 設定
# .gitignore
.env
.env.local
.env.*.local
.env.production
.env.staging
secrets/
# テンプレートはコミットする
!.env.example
!.env.development.example# .env.example (テンプレートとしてコミット)
COMPOSE_PROJECT_NAME=myapp
POSTGRES_VERSION=16
NODE_VERSION=20
DB_PASSWORD=<SET_YOUR_PASSWORD>
API_KEY=<SET_YOUR_API_KEY>4. 複数 Compose ファイルのマージ
4.1 オーバーライドパターン
Docker Compose は複数の設定ファイルをマージして一つの構成を作成できる。これにより、ベース設定と環境固有の設定を分離し、DRY な構成管理を実現できる。
# docker-compose.yml (ベース設定)
services:
app:
build: .
environment:
NODE_ENV: production
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:# docker-compose.override.yml (開発用オーバーライド。自動マージ)
services:
app:
build:
target: development
environment:
NODE_ENV: development
DEBUG: "true"
volumes:
- .:/app
- node_modules:/app/node_modules
ports:
- "3000:3000"
- "9229:9229" # デバッガポート
db:
ports:
- "5432:5432" # 開発時のみ外部公開
environment:
POSTGRES_PASSWORD: postgres
volumes:
node_modules:# docker-compose.prod.yml (本番用)
services:
app:
restart: always
deploy:
replicas: 2
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
db:
restart: always
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
redis:
restart: always
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru# docker-compose.ci.yml (CI 専用)
services:
app:
build:
target: test
environment:
NODE_ENV: test
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp_test
db:
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp_test
tmpfs:
- /var/lib/postgresql/data # CI ではメモリ上で高速化4.2 マージのコマンド
# 開発 (compose.yml + compose.override.yml を自動マージ)
docker compose up -d
# 本番 (override を除外し、prod を適用)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# CI (override を除外し、ci を適用)
docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d
# 設定のマージ結果を確認
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
# 特定のサービスのみマージ結果を確認
docker compose -f docker-compose.yml -f docker-compose.prod.yml config --services
# マージ結果をファイルに出力
docker compose -f docker-compose.yml -f docker-compose.prod.yml config > docker-compose.resolved.yml4.3 マージの規則詳細
| 設定項目 | マージ動作 |
|---|---|
image, command, entrypoint |
後のファイルで上書き |
environment |
マージ(キー単位で上書き) |
volumes |
マージ(追加される) |
ports |
マージ(追加される) |
networks |
マージ(追加される) |
labels |
マージ(キー単位で上書き) |
deploy |
ディープマージ |
build.args |
マージ(キー単位で上書き) |
healthcheck |
後のファイルで完全上書き |
4.4 COMPOSE_FILE 環境変数による自動選択
# .env ファイルで読み込むファイルを指定
# 開発環境
COMPOSE_FILE=docker-compose.yml:docker-compose.override.yml
# ステージング環境
COMPOSE_FILE=docker-compose.yml:docker-compose.staging.yml
# 本番環境
COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml
# 区切り文字はデフォルトで「:」(Linux/macOS) または「;」(Windows)5. リソース制限とロギング
5.1 リソース制限
services:
app:
deploy:
resources:
limits:
cpus: '0.5' # CPU 0.5 コア
memory: 256M # メモリ 256MB
pids: 100 # プロセス数上限
reservations:
cpus: '0.25' # 最低保証 CPU
memory: 128M # 最低保証メモリ
# OOM 時の動作
oom_kill_disable: false
oom_score_adj: 100 # OOM スコア調整 (-1000 to 1000)
# ファイルディスクリプタ制限
ulimits:
nofile:
soft: 65536
hard: 65536
nproc:
soft: 4096
hard: 4096
# SHM サイズ制限 (共有メモリ)
shm_size: '256m'
# ストップシグナルとタイムアウト
stop_signal: SIGTERM
stop_grace_period: 30s5.2 各サービスの推奨リソース設定
services:
# Node.js アプリ
app:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
# PostgreSQL
db:
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
shm_size: '256m' # PostgreSQL は共有メモリを多用
# Redis
redis:
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 64M
# Elasticsearch
elasticsearch:
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 1G
environment:
ES_JAVA_OPTS: "-Xms512m -Xmx1g" # JVM ヒープもメモリ制限に合わせる5.3 ロギング設定
services:
app:
logging:
driver: json-file
options:
max-size: "10m" # ログファイル最大サイズ
max-file: "3" # ローテーション数
compress: "true" # 圧縮
labels: "service"
tag: "{{.Name}}/{{.ID}}" # ログタグのカスタマイズ
# 全サービス共通のログ設定 (YAML アンカー)
db:
logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
redis:
logging: *default-logging # アンカーを参照5.4 外部ロギングドライバーの設定
services:
# Fluentd ドライバー
app:
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: myapp.{{.Name}}
fluentd-async: "true"
fluentd-retry-wait: "1s"
fluentd-max-retries: "10"
# syslog ドライバー
api:
logging:
driver: syslog
options:
syslog-address: "tcp://logserver:514"
syslog-facility: "daemon"
tag: "{{.Name}}"
# ログを無効化 (出力が多すぎるサービス)
noisy-service:
logging:
driver: none6. YAML アンカーとエイリアス
6.1 基本的なアンカーとエイリアス
# 共通設定をアンカーで定義
x-common-env: &common-env
TZ: Asia/Tokyo
LANG: ja_JP.UTF-8
x-healthcheck-defaults: &healthcheck-defaults
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
app:
environment:
<<: *common-env # マージ (アンカー展開)
NODE_ENV: production
healthcheck:
<<: *healthcheck-defaults
test: ["CMD-SHELL", "curl -f http://localhost:3000/health"]
logging: *default-logging
worker:
environment:
<<: *common-env
WORKER_TYPE: background
healthcheck:
<<: *healthcheck-defaults
test: ["CMD-SHELL", "curl -f http://localhost:3001/health"]
logging: *default-logging6.2 Extension Fields (x- プレフィックス) の高度な活用
Extension Fields は Compose が解釈しないカスタムフィールドで、アンカーの定義場所として使用する。サービス定義全体を共通化する場合に特に効果的である。
# サービスのテンプレート
x-app-base: &app-base
build:
context: .
dockerfile: Dockerfile
restart: always
networks:
- app-net
logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
environment: &common-env
TZ: Asia/Tokyo
LANG: ja_JP.UTF-8
NODE_ENV: production
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
REDIS_URL: redis://redis:6379
x-db-healthcheck: &db-healthcheck
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
services:
# テンプレートを継承してカスタマイズ
web:
<<: *app-base
ports:
- "3000:3000"
command: node dist/web.js
environment:
<<: *common-env
SERVER_TYPE: web
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
api:
<<: *app-base
ports:
- "8080:8080"
command: node dist/api.js
environment:
<<: *common-env
SERVER_TYPE: api
worker:
<<: *app-base
command: node dist/worker.js
environment:
<<: *common-env
SERVER_TYPE: worker
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G # ワーカーはメモリを多く使う
scheduler:
<<: *app-base
command: node dist/scheduler.js
environment:
<<: *common-env
SERVER_TYPE: scheduler
db:
image: postgres:16-alpine
restart: always
healthcheck:
<<: *db-healthcheck
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- app-net
logging: *default-logging
networks:
app-net:
volumes:
pgdata:6.3 条件分岐的な設定(アンカーとオーバーライドの組み合わせ)
# docker-compose.yml
x-app-volumes: &app-volumes
volumes:
- app-data:/data
services:
app:
<<: *app-volumes
image: myapp:latest# docker-compose.override.yml (開発環境で上書き)
services:
app:
volumes:
- .:/app
- app-data:/data # 元の Volume も維持7. ネットワーク分離の高度な設定
7.1 マルチネットワーク構成
services:
# フロントエンド (public + app-tier のみ)
nginx:
image: nginx:alpine
networks:
- public
- app-tier
ports:
- "80:80"
- "443:443"
# アプリケーション (app-tier + data-tier)
app:
build: .
networks:
- app-tier
- data-tier
- cache-tier
# データベース (data-tier のみ / 外部アクセス不可)
db:
image: postgres:16-alpine
networks:
- data-tier
# Redis (cache-tier のみ)
redis:
image: redis:7-alpine
networks:
- cache-tier
networks:
public:
driver: bridge
app-tier:
driver: bridge
data-tier:
driver: bridge
internal: true # 外部アクセスを完全遮断
cache-tier:
driver: bridge
internal: true7.2 IP アドレスの固定
services:
app:
networks:
app-net:
ipv4_address: 172.28.0.10
db:
networks:
app-net:
ipv4_address: 172.28.0.20
networks:
app-net:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/24
gateway: 172.28.0.18. 高度な設定比較
| 機能 | 基本設定 | 応用設定 |
|---|---|---|
| サービス起動 | depends_on: [db] |
depends_on: { db: { condition: service_healthy } } |
| 環境変数 | environment: {KEY: val} |
env_file + secrets + 優先順位管理 |
| ネットワーク | デフォルト | internal: true + 複数ネットワーク分離 |
| ログ | デフォルト (無制限) | json-file + max-size + max-file |
| リソース | 無制限 | deploy.resources.limits で CPU/メモリ制限 |
| プロファイル | 全サービス起動 | profiles で用途別グルーピング |
| 設定管理 | 単一ファイル | override.yml + prod.yml でレイヤー化 |
| ヘルスチェック | なし | サービスごとの専用チェック + カスタムスクリプト |
| シークレット | 環境変数に直接記載 | secrets + *_FILE パターン |
| YAML 再利用 | コピー&ペースト | x- Extension Fields + アンカー |
9. Compose の便利なコマンド集
9.1 日常操作
# サービスの状態確認
docker compose ps
docker compose ps -a # 停止中のコンテナも表示
# ログの確認
docker compose logs -f # 全サービスのログをフォロー
docker compose logs -f app worker # 特定サービスのみ
docker compose logs --tail=50 app # 最新50行
docker compose logs --since=1h # 直近1時間のログ
# サービスの再起動
docker compose restart app # app のみ再起動
docker compose up -d --force-recreate app # 強制再作成
# 設定の確認
docker compose config # マージ結果を表示
docker compose config --services # サービス一覧
docker compose config --volumes # ボリューム一覧
# イメージのビルド
docker compose build # 全サービスビルド
docker compose build --no-cache # キャッシュなしでビルド
docker compose build --parallel # 並列ビルド
docker compose build app worker # 特定サービスのみ
# コンテナ内でコマンド実行
docker compose exec app sh # シェルに入る
docker compose exec -T app npm run migrate # TTY なし(スクリプト向け)
docker compose run --rm app npm test # ワンショット実行9.2 クリーンアップ
# サービス停止
docker compose stop # 停止のみ
docker compose down # 停止 + コンテナ削除
docker compose down -v # 停止 + コンテナ + ボリューム削除
docker compose down --remove-orphans # 孤立コンテナも削除
docker compose down --rmi local # ローカルイメージも削除
docker compose down -v --rmi all # 全て削除
# 特定サービスのみ停止
docker compose stop app
docker compose rm -f appアンチパターン
アンチパターン 1: ヘルスチェックなしの depends_on
# NG: condition 未指定 → コンテナ起動 = サービス利用可能 と誤解
services:
app:
depends_on:
- db # DB コンテナが起動した瞬間に app も起動する
# → DB がまだ接続受付前でアプリがクラッシュ
# OK: healthcheck + condition で確実に待機
services:
app:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5問題点: PostgreSQL コンテナが起動してから実際に接続を受け付けるまでに数秒〜十数秒かかる。ヘルスチェックなしでは、アプリが接続エラーでクラッシュし、手動で再起動が必要になる。
アンチパターン 2: ログのローテーション未設定
# NG: ログ設定なし → ディスクが枯渇する
services:
app:
image: myapp:latest
# logging 未設定 → json-file ドライバ、サイズ無制限
# OK: ログサイズとローテーションを設定
services:
app:
image: myapp:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"問題点: Docker のデフォルトログドライバ (json-file) はサイズ無制限でログを蓄積する。長時間稼働するサービスではログファイルがディスクを圧迫し、最終的にホストマシンのディスクが枯渇してシステム全体が停止する。
アンチパターン 3: シークレットを環境変数に直接記載
# NG: パスワードをファイルに直接記載
services:
db:
environment:
POSTGRES_PASSWORD: my_super_secret_password # Git にコミットされる
# OK: .env ファイルまたは secrets を使用
services:
db:
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # .env から取得
# または secrets を使用
secrets:
- db_password問題点: docker-compose.yml にハードコードされたパスワードは Git リポジトリにコミットされ、漏洩リスクが極めて高い。.env ファイルを .gitignore に追加するか、Docker の secrets 機能を使用する。
アンチパターン 4: リソース制限なしの本番運用
# NG: リソース制限なし → 1つのサービスがホストのリソースを食い尽くす
services:
app:
image: myapp:latest
# deploy.resources 未設定
# OK: 適切なリソース制限を設定
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M問題点: メモリリークやCPU 暴走が発生した場合、制限がないとホスト全体のリソースが枯渇し、他のサービスや SSH 接続すらも影響を受ける。特に本番環境では必ず制限を設定する。
実践演習
演習1: 基本的な実装
以下の要件を満たすコードを実装してください。
要件:
- 入力データの検証を行うこと
- エラーハンドリングを適切に実装すること
- テストコードも作成すること
# 演習1: 基本実装のテンプレート
class Exercise1:
"""基本的な実装パターンの演習"""
def __init__(self):
self.data = []
def validate_input(self, value):
"""入力値の検証"""
if value is None:
raise ValueError("入力値がNoneです")
return True
def process(self, value):
"""データ処理のメインロジック"""
self.validate_input(value)
self.data.append(value)
return self.data
def get_results(self):
"""処理結果の取得"""
return {
'count': len(self.data),
'data': self.data
}
# テスト
def test_exercise1():
ex = Exercise1()
assert ex.process(1) == [1]
assert ex.process(2) == [1, 2]
assert ex.get_results()['count'] == 2
try:
ex.process(None)
assert False, "例外が発生するべき"
except ValueError:
pass
print("全テスト合格!")
test_exercise1()演習2: 応用パターン
基本実装を拡張して、以下の機能を追加してください。
# 演習2: 応用パターン
from typing import List, Dict, Optional
from datetime import datetime
class AdvancedExercise:
"""応用パターンの演習"""
def __init__(self, max_size: int = 100):
self._items: List[Dict] = []
self._max_size = max_size
self._created_at = datetime.now()
def add(self, key: str, value: any) -> bool:
"""アイテムの追加(サイズ制限付き)"""
if len(self._items) >= self._max_size:
return False
self._items.append({
'key': key,
'value': value,
'timestamp': datetime.now().isoformat()
})
return True
def find(self, key: str) -> Optional[Dict]:
"""キーによる検索"""
for item in reversed(self._items):
if item['key'] == key:
return item
return None
def remove(self, key: str) -> bool:
"""キーによる削除"""
for i, item in enumerate(self._items):
if item['key'] == key:
self._items.pop(i)
return True
return False
def stats(self) -> Dict:
"""統計情報"""
return {
'total_items': len(self._items),
'max_size': self._max_size,
'usage_percent': len(self._items) / self._max_size * 100,
'uptime': str(datetime.now() - self._created_at)
}
# テスト
def test_advanced():
ex = AdvancedExercise(max_size=3)
assert ex.add("a", 1) == True
assert ex.add("b", 2) == True
assert ex.add("c", 3) == True
assert ex.add("d", 4) == False # サイズ制限
assert ex.find("b")['value'] == 2
assert ex.remove("b") == True
assert ex.find("b") is None
stats = ex.stats()
assert stats['total_items'] == 2
print("応用テスト全合格!")
test_advanced()演習3: パフォーマンス最適化
以下のコードのパフォーマンスを改善してください。
# 演習3: パフォーマンス最適化
import time
from functools import lru_cache
# 最適化前(O(n^2))
def slow_search(data: list, target: int) -> int:
"""非効率な検索"""
for i in range(len(data)):
for j in range(i + 1, len(data)):
if data[i] + data[j] == target:
return (i, j)
return (-1, -1)
# 最適化後(O(n))
def fast_search(data: list, target: int) -> tuple:
"""ハッシュマップを使った効率的な検索"""
seen = {}
for i, num in enumerate(data):
complement = target - num
if complement in seen:
return (seen[complement], i)
seen[num] = i
return (-1, -1)
# ベンチマーク
def benchmark():
import random
data = list(range(5000))
random.shuffle(data)
target = data[100] + data[4000]
start = time.time()
result1 = slow_search(data, target)
slow_time = time.time() - start
start = time.time()
result2 = fast_search(data, target)
fast_time = time.time() - start
print(f"非効率版: {slow_time:.4f}秒")
print(f"効率版: {fast_time:.6f}秒")
print(f"高速化率: {slow_time/fast_time:.0f}倍")
benchmark()ポイント:
- アルゴリズムの計算量を意識する
- 適切なデータ構造を選択する
- ベンチマークで効果を測定する
トラブルシューティング
よくあるエラーと解決策
| エラー | 原因 | 解決策 |
|---|---|---|
| 初期化エラー | 設定ファイルの不備 | 設定ファイルのパスと形式を確認 |
| タイムアウト | ネットワーク遅延/リソース不足 | タイムアウト値の調整、リトライ処理の追加 |
| メモリ不足 | データ量の増大 | バッチ処理の導入、ページネーションの実装 |
| 権限エラー | アクセス権限の不足 | 実行ユーザーの権限確認、設定の見直し |
| データ不整合 | 並行処理の競合 | ロック機構の導入、トランザクション管理 |
デバッグの手順
- エラーメッセージの確認: スタックトレースを読み、発生箇所を特定する
- 再現手順の確立: 最小限のコードでエラーを再現する
- 仮説の立案: 考えられる原因をリストアップする
- 段階的な検証: ログ出力やデバッガを使って仮説を検証する
- 修正と回帰テスト: 修正後、関連する箇所のテストも実行する
# デバッグ用ユーティリティ
import logging
import traceback
from functools import wraps
# ロガーの設定
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
logger = logging.getLogger(__name__)
def debug_decorator(func):
"""関数の入出力をログ出力するデコレータ"""
@wraps(func)
def wrapper(*args, **kwargs):
logger.debug(f"呼び出し: {func.__name__}(args={args}, kwargs={kwargs})")
try:
result = func(*args, **kwargs)
logger.debug(f"戻り値: {func.__name__} -> {result}")
return result
except Exception as e:
logger.error(f"例外発生: {func.__name__}: {e}")
logger.error(traceback.format_exc())
raise
return wrapper
@debug_decorator
def process_data(items):
"""データ処理(デバッグ対象)"""
if not items:
raise ValueError("空のデータ")
return [item * 2 for item in items]パフォーマンス問題の診断
パフォーマンス問題が発生した場合の診断手順:
- ボトルネックの特定: プロファイリングツールで計測
- メモリ使用量の確認: メモリリークの有無をチェック
- I/O待ちの確認: ディスクやネットワークI/Oの状況を確認
- 同時接続数の確認: コネクションプールの状態を確認
| 問題の種類 | 診断ツール | 対策 |
|---|---|---|
| CPU負荷 | cProfile, py-spy | アルゴリズム改善、並列化 |
| メモリリーク | tracemalloc, objgraph | 参照の適切な解放 |
| I/Oボトルネック | strace, iostat | 非同期I/O、キャッシュ |
| DB遅延 | EXPLAIN, slow query log | インデックス、クエリ最適化 |
FAQ
Q1: YAML アンカーと Extension Fields (x- プレフィックス) の違いは何ですか?
A: YAML アンカー (& / *) は YAML 標準の参照機構で、同じ値を複数箇所で再利用する。Extension Fields (x- プレフィックス) は Compose 仕様の機能で、Compose が無視するカスタムフィールドを定義できる。両者を組み合わせ、x-common: &common でトップレベルに共通設定を定義し、各サービスで <<: *common で展開するのが一般的なパターン。
Q2: プロファイルと複数 Compose ファイルのどちらを使うべきですか?
A: 同じ docker-compose.yml 内で用途別にサービスをグルーピングしたい場合はプロファイルが適切(例: debug ツール、監視ツール)。環境全体の設定を切り替えたい場合(開発 vs 本番、ポート公開の有無、リソース制限など)は複数ファイルのオーバーライドが適切。両方を併用することもできる。
Q3: Compose で使える Interpolation (変数展開) の構文は?
A: ${VARIABLE} が基本形。デフォルト値は ${VARIABLE:-default} (未設定時) と ${VARIABLE-default} (未定義時のみ)。エラーにする場合は ${VARIABLE:?error message}。これらは .env ファイルまたはシェルの環境変数から値を取得する。Compose ファイル内の environment: セクションの値は展開されるが、コンテナ内での展開とは異なる点に注意。
Q4: depends_on の restart: true オプションは何ですか?
A: Compose V2.20+ で追加された機能で、依存先のサービスが再起動された場合に、依存元のサービスも自動的に再起動させる。例えば、DB コンテナが再起動された場合に、自動的にアプリコンテナも再起動させたい場合に使用する。
services:
app:
depends_on:
db:
condition: service_healthy
restart: true # db が再起動されたら app も再起動Q5: healthcheck の start_period はどう設定すべきですか?
A: start_period はサービスの初期化に必要な時間を見積もって設定する。この期間中のヘルスチェック失敗はリトライ回数にカウントされない。PostgreSQL なら 30 秒、Elasticsearch なら 60 秒程度が目安。テスト環境で docker compose up してから実際にサービスが応答するまでの時間を計測し、その値の 1.5〜2 倍を設定するのが安全である。
Q6: Compose V2 と V1 (docker-compose コマンド) の違いは?
A: Compose V2 は docker compose(ハイフンなし)で呼び出す Go 言語で実装されたプラグインである。V1 は docker-compose(ハイフンあり)で呼び出す Python 実装で、2023 年に EOL となった。V2 では version: フィールドが不要になり、profiles、watch、include など多くの新機能が追加されている。新規プロジェクトでは必ず V2 を使用すべきである。
まとめ
| 項目 | 要点 |
|---|---|
| プロファイル | profiles: でサービスをグルーピングし、--profile で選択起動 |
| depends_on | condition: service_healthy で確実な起動順序を保証 |
| healthcheck | DB/Redis/HTTP それぞれに適切なチェックコマンドを設定 |
| 環境変数 | .env (Compose変数) + env_file (アプリ変数) + secrets の使い分け |
| ファイルマージ | override.yml (開発) + prod.yml (本番) でレイヤー化 |
| YAML アンカー | x- Extension Fields + アンカーで設定の DRY 化 |
| リソース制限 | deploy.resources.limits で CPU/メモリを制限 |
| ログ管理 | max-size + max-file でディスク枯渇を防止 |
| ネットワーク分離 | internal: true + 複数ネットワークでセキュリティ強化 |
| シークレット管理 | secrets + *_FILE パターンで安全に機密情報を管理 |
次に読むべきガイド
- Compose 開発ワークフロー -- ホットリロード、デバッグ、CI 統合
- Docker Compose 基礎 -- 基本構文の復習
- コンテナセキュリティ -- セキュリティのベストプラクティス
参考文献
- Compose Specification - Profiles -- https://docs.docker.com/compose/profiles/ -- プロファイル機能の公式ドキュメント
- Compose Specification - Healthcheck -- https://docs.docker.com/compose/compose-file/05-services/#healthcheck -- ヘルスチェック設定の詳細
- Environment variables in Compose -- https://docs.docker.com/compose/environment-variables/ -- 環境変数の優先順位と管理方法
- Compose file merge -- https://docs.docker.com/compose/multiple-compose-files/ -- 複数ファイルのマージ規則
- Compose Specification - Extension Fields -- https://docs.docker.com/compose/compose-file/11-extension/ -- Extension Fields の仕様
- Compose Specification - Secrets -- https://docs.docker.com/compose/use-secrets/ -- シークレット管理の公式ドキュメント
- Docker Compose V2 Release Notes -- https://docs.docker.com/compose/release-notes/ -- V2 の新機能と変更点