Docker Composeで複数サービスを統合する実践的な書き方

本記事では、Docker Composeの設定ファイル(docker-compose.yml)を正しく書くための実践的なテクニックを解説します。マイクロサービス環境でWebアプリ・データベース・キャッシュを同時に立ち上げる具体例を通じて、すぐに仕事で活用できるパターンを習得できます。

Docker Composeの基本構造を理解する

Docker Composeは複数のコンテナを一度に定義・実行するツールです。YAML形式の設定ファイル(docker-compose.yml)に記述します。単一のコンテナで事足りる場合はDocker CLIで十分ですが、複数サービスの依存関係やネットワーク設定が必要な場面でDocker Composeが活躍します。

使うべき場面: 開発環境・テスト環境構築、ローカルでのマイクロサービス検証

使うべきでない場面: 本番環境での大規模オーケストレーション(Kubernetesを検討)、単一コンテナの運用

YAMLファイルの基本スキーマ

docker-compose.ymlは以下の構造が基本になります。

version: '3.8'

services:
  # サービス定義
  web:
    image: nginx:latest
    ports:
      - "8080:80"
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: password123

volumes:
  # 名前付きボリューム定義
  db_data:

networks:
  # ネットワーク定義
  default:
    driver: bridge

実践的なマルチサービス構成の書き方

Webアプリ + PostgreSQL + Redisの統合例

開発現場でよく使う3層構成を実装してみます。このパターンをマスターすれば、ほとんどのDocker Compose案件に対応できます。

version: '3.8'

services:
  # Node.jsのWebアプリケーション
  api:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: app_api
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://user:password@postgres:5432/myapp
      REDIS_URL: redis://redis:6379
      NODE_ENV: development
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - .:/app
      - /app/node_modules
    networks:
      - backend
    restart: unless-stopped

  # PostgreSQLデータベース
  postgres:
    image: postgres:15-alpine
    container_name: app_postgres
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend
    restart: unless-stopped

  # Redisキャッシュ
  redis:
    image: redis:7-alpine
    container_name: app_redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - backend
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

networks:
  backend:
    driver: bridge

重要な設定項目の解説

build vs image: buildを使用するとDockerfileをビルドし、imageを使用するとレジストリから直接プルします。開発中はbuildで柔軟に対応し、本番ではimageで安定性を重視します。

depends_on: サービス起動順序を制御します。conditionパラメータで起動完了の判定方法を指定できます。service_healthyを使う場合、対象サービスにhealthcheckの設定が必須です。

healthcheck: サービスの健全性をチェックします。PostgreSQLの場合、pg_isreadyコマンドでの確認が標準的です。起動時間が長いサービスでは重要な役割を果たします。

volumes: コンテナ内ディレクトリをホストマシンやボリュームにマウントします。ホストパス:コンテナパスで指定します。開発時にソースコードをマウントすることでホットリロードが可能になります。

よくあるハマりポイントと解決策

サービス間通信が失敗する場合

問題: APIコンテナからlocalhostでPostgresに接続しようとするとエラーが出る。

原因: コンテナ内のlocalhostはコンテナ自身を指しており、別のコンテナには到達しません。

解決策: サービス名でホスト名を指定します。docker-composeは自動的にサービス名をDNS名として解決します。上記の例では、APIコンテナからpostgresql://user:password@postgres:5432/myappでPostgresに接続できます。

ボリュームがマウントされない

問題: ホストマシンの変更がコンテナに反映されない。

原因: 相対パスの解釈、パーミッション問題、またはコンテナが古いイメージで起動している。

解決策: 以下を確認します。

# 既存のコンテナ・イメージを全削除して再構築
docker-compose down -v
docker-compose up --build

# ボリュームマウント状況を確認
docker inspect コンテナ名 | grep Mounts

起動順序の問題でDBにアクセスできない

問題: APIが起動してもPostgresがまだ完全に初期化されていない。

原因: depends_onでservice_startedを指定すると、プロセスが起動しただけの状態で依存サービスが起動してしまう。

解決策: healthcheckを設定し、depends_onのconditionをservice_healthyに指定します。上記の例がこの実装パターンです。

本番環境に向けた設定のコツ

環境変数の外部管理

パスワードやAPIキーをdocker-compose.ymlに直書きしてはいけません。.envファイルで管理します。

# .env ファイル
POSTGRES_USER=produser
POSTGRES_PASSWORD=SecurePass123!
DATABASE_URL=postgresql://produser:SecurePass123!@postgres:5432/myapp
NODE_ENV=production

# docker-compose.yml内で参照
environment:
  DATABASE_URL: ${DATABASE_URL}
  NODE_ENV: ${NODE_ENV}

Gitに.envをコミットしないよう.gitignoreに追加してください。

ネットワーク分離

複数のプロジェクトを同時に実行する場合、ネットワーク分離が重要です。

networks:
  backend:
    driver: bridge
    name: myapp_backend
  
  frontend:
    driver: bridge
    name: myapp_frontend

明示的にネットワーク名を指定することで、名前衝突を回避できます。

Docker Composeとの比較・代替ツール

Podman Compose: Dockerの代替ランタイムであるPodmanのCompose互換ツール。Dockerless環境で利用できますが、互換性が100%ではありません。

Kubernetes(kubectl): 本番環境での標準的なオーケストレーションツール。複雑なデプロイメント、スケーリング、ローリングアップデートが必要な場合に選択します。

実際の動作確認方法

以下のコマンドで動作確認できます(テスト環境: macOS 14 / Docker 25.0 / docker-compose 2.24)。

# コンテナの起動
docker-compose up -d

# ログの確認
docker-compose logs -f api

# 特定サービスへのアクセス確認
docker-compose exec api curl http://postgres:5432

# コンテナの停止
docker-compose down

# ボリュームまで削除
docker-compose down -v

よくある質問

A: 最新の3.8を推奨します。Docker Compose v2では1と2のバージョン値に意味がなくなりましたが、互換性のため3.xを記述するのが慣例です。古いDocker環境では2.1や3.0も選択肢ですが、新規プロジェクトは3.8以上を使用してください。

A: はい、docker-compose.ymlで可能です。serviceセクション内にcommandやentrypointキーを追加すると、イメージに含まれる設定を上書きできます。例えば開発環境でノーダイスターターツール(nodemon)を使う場合、command: npm run dev のように指定します。

A: ports設定でコンテナ側のみ指定すれば、ホスト側は自動割り当てされます。例えば ports: - "8080" とするとランダムなポートがホストに割り当てられます。実際の割り当て結果は docker-compose ps で確認できます。

まとめ

  • docker-compose.ymlはversion、services、volumes、networksの4要素で構成される
  • depends_onとhealthcheckを組み合わせることで確実な起動順序制御ができる
  • サービス名がDNS名として自動解決されるため、ホスト名として直接指定可能
  • 環境変数は.envファイルで外部管理し、パスワードなどの機密情報は直書きしない
  • development環境ではbuildでイメージを構築、production環境ではimageで明示的なバージョンを指定
  • 複雑な本番構成ではKubernetesへの移行を検討する

Docker Composeを適切に設定することで、チーム全体で同じ開発環境を再現できます。本記事のテンプレートを活用し、プロジェクトに合わせてカスタマイズしてみてください。

Docker公式ドキュメント - Docker Composeも併せて参照すると、より深い理解が得られます。

K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →