開発チームが拡大するにつれ、「自分のマシンでは動くのに」という問題が頻繁に発生するようになります。Docker Composeを活用すれば、複数のサービスを一括管理し、チーム全員が同一の開発環境を即座に立ち上げることができます。本記事では、実務で培ったマルチコンテナ構成のベストプラクティスを具体的なコード例とともに解説します。
まず、典型的なWebアプリケーション構成として、アプリケーションサーバー、データベース、キャッシュの3コンテナ構成を考えます。
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- node_modules:/app/node_modules
environment:
- DATABASE_URL=postgres://user:pass@db:5432/myapp_dev
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=myapp_dev
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
node_modules:
postgres_data:
redis_data:
ここで重要なのは、depends_onにヘルスチェック条件を付けている点です。単にコンテナが起動しただけでは、PostgreSQLが接続を受け付ける準備ができているとは限りません。service_healthy条件を使うことで、データベースが確実に利用可能になってからアプリケーションが起動します。
Docker Composeには設定ファイルのオーバーライド機能があり、これを活用して環境ごとの差異を管理します。
# docker-compose.override.yml(開発環境用、自動読み込み)
services:
app:
build:
target: development
volumes:
- .:/app
command: npm run dev
environment:
- DEBUG=true
- LOG_LEVEL=debug
# docker-compose.prod.yml(本番環境用)
services:
app:
build:
target: production
restart: always
environment:
- NODE_ENV=production
- LOG_LEVEL=info
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
開発時は単にdocker compose upとすればdocker-compose.ymlとdocker-compose.override.ymlが自動的にマージされます。本番環境ではdocker compose -f docker-compose.yml -f docker-compose.prod.yml upと明示的に指定します。
Dockerfileでマルチステージビルドを使うと、開発用と本番用のイメージを一つのファイルで管理できます。
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS production
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["node", "dist/main.js"]
開発環境でバインドマウントを使う際、よく遭遇するのがNode.jsのnode_modules問題です。ホストとコンテナでnode_modulesを共有するとネイティブバイナリの互換性問題が発生します。名前付きボリュームでnode_modulesを分離するパターンは必須テクニックです。
また、データベースのボリュームに関しては、開発中にスキーマを大幅に変更した場合などにデータを初期化したいことがあります。その際は以下のコマンドが便利です。
# ボリュームごと破棄して再構築
docker compose down -v
docker compose up --build
Docker Composeはデフォルトで一つのブリッジネットワークを作成し、サービス名でDNS解決が行われます。しかし、マイクロサービス構成ではネットワークを分離したいケースがあります。
services:
frontend:
networks:
- frontend_net
api:
networks:
- frontend_net
- backend_net
db:
networks:
- backend_net
networks:
frontend_net:
backend_net:
この構成では、frontendからdbに直接アクセスすることはできず、必ずapiを経由する設計を強制できます。セキュリティの観点からも有効なパターンです。
実務では、コンテナの動作確認やログ調査が頻繁に必要になります。覚えておくべきコマンドをまとめます。
# 特定サービスのログをリアルタイムで確認
docker compose logs -f app
# コンテナ内に入って調査
docker compose exec app sh
# サービスの状態一覧
docker compose ps
# 特定サービスだけ再起動
docker compose restart app
portsのホスト側ポート番号を変更するdocker compose up --build --force-recreateで解決docker compose exec app ping dbで疎通確認するdocker system prune -a --volumesで一括削除するDocker Composeは開発環境だけでなく、CIパイプラインでのテスト実行にも活用できます。テスト用のcomposeファイルを用意し、テスト完了後にコンテナを自動破棄するのがポイントです。
# CI環境でのテスト実行例
docker compose -f docker-compose.yml -f docker-compose.test.yml run --rm app npm test
docker compose down -v
GitHub ActionsやGitLab CIと組み合わせれば、PR作成時にマルチコンテナ環境でのインテグレーションテストを自動実行できます。
Docker Composeによるマルチコンテナ構成は、チーム開発の生産性を大きく向上させます。重要なポイントを振り返ります。
最初の設計に少し時間をかけるだけで、その後の開発体験が大きく変わります。ぜひチームの開発環境に導入してみてください。