Docker Compose実践ガイド|開発環境を一発で構築する設定術

kento_morota 20分で読めます

「新しいメンバーが開発環境を構築するのに丸一日かかった」「自分の環境では動くのに、他のメンバーの環境では動かない」「Node.js、PostgreSQL、Redisのバージョンがバラバラ」――チーム開発で頻発するこうした問題を解決するのがDocker Composeです。

本記事では、Docker Composeを使って開発環境を一発で構築する方法を、実際のプロジェクト構成を例に解説します。基本的な使い方から、マルチステージビルド、環境変数の管理、開発と本番の使い分けまで、実践的なテクニックを網羅します。

Docker Composeとは?なぜ必要なのか

Docker Composeは、複数のDockerコンテナをYAMLファイルで定義し、一括で管理するツールです。Dockerの最新版には標準で含まれています。

Docker Composeが解決する問題

モダンなWebアプリケーションは、アプリケーションサーバー、データベース、キャッシュ、メッセージキューなど、複数のサービスで構成されます。これらを個別にdocker runコマンドで起動・管理するのは現実的ではありません。

# Docker Composeなしの場合:個別にコンテナを起動
docker network create myapp-network

docker run -d --name postgres \
  --network myapp-network \
  -e POSTGRES_PASSWORD=password \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

docker run -d --name redis \
  --network myapp-network \
  redis:7-alpine

docker run -d --name app \
  --network myapp-network \
  -p 3000:3000 \
  -e DATABASE_URL=postgresql://postgres:password@postgres:5432/mydb \
  -e REDIS_URL=redis://redis:6379 \
  myapp:latest

# 停止するときも個別に...
docker stop app redis postgres
docker rm app redis postgres
# Docker Composeなら1コマンド
docker compose up -d    # 起動
docker compose down      # 停止・削除

Docker Composeのバージョン

Docker Compose V2はdocker compose(ハイフンなし)コマンドで実行します。旧バージョンのdocker-compose(ハイフンあり)は非推奨です。

# バージョン確認
docker compose version
# Docker Compose version v2.x.x

基本的なcompose.ymlの書き方

Docker Composeの設定ファイルはcompose.yml(またはdocker-compose.yml)です。

最小構成の例

# compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:password@db:5432/mydb
    depends_on:
      - db
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

基本コマンド

# すべてのサービスをバックグラウンドで起動
docker compose up -d

# ログの確認
docker compose logs -f          # 全サービス
docker compose logs -f app      # 特定サービス

# サービスの状態確認
docker compose ps

# コンテナ内でコマンド実行
docker compose exec app sh
docker compose exec db psql -U postgres -d mydb

# イメージの再ビルド
docker compose up -d --build

# すべて停止して削除(ボリュームは保持)
docker compose down

# ボリュームも含めて完全に削除
docker compose down -v

実践的な開発環境構成

Next.js + PostgreSQL + Redis の開発環境を構築する実例を紹介します。

プロジェクト構成

my-project/
├── compose.yml
├── compose.prod.yml
├── Dockerfile
├── Dockerfile.prod
├── .env
├── .env.example
├── src/
├── prisma/
│   └── schema.prisma
└── ...

開発用Dockerfile

# Dockerfile(開発用)
FROM node:20-alpine

WORKDIR /app

# パッケージファイルのコピーと依存関係のインストール
COPY package.json package-lock.json ./
RUN npm ci

# ソースコードはvolumesでマウントするのでCOPYしない

EXPOSE 3000

CMD ["npm", "run", "dev"]

開発用compose.yml

# compose.yml
services:
  # ===================
  # アプリケーション
  # ===================
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    env_file:
      - .env
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - .:/app
      - /app/node_modules    # node_modulesはコンテナ内のものを使用
      - /app/.next           # .nextもコンテナ内のものを使用
    restart: unless-stopped

  # ===================
  # PostgreSQL
  # ===================
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./docker/db/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # ===================
  # Redis
  # ===================
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # ===================
  # Mailpit(開発用メールサーバー)
  # ===================
  mailpit:
    image: axllent/mailpit
    ports:
      - "8025:8025"   # Web UI
      - "1025:1025"   # SMTP
    restart: unless-stopped

  # ===================
  # pgAdmin(DB管理GUI)
  # ===================
  pgadmin:
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin
    ports:
      - "5050:80"
    depends_on:
      - db
    restart: unless-stopped
    profiles:
      - tools   # docker compose --profile tools up で起動

volumes:
  pgdata:
  redis-data:

環境変数ファイル

# .env.example(リポジトリにコミット)
DB_PASSWORD=password
DB_NAME=mydb
SMTP_HOST=mailpit
SMTP_PORT=1025
# .env(.gitignoreに追加、リポジトリにコミットしない)
DB_PASSWORD=password
DB_NAME=mydb
SMTP_HOST=mailpit
SMTP_PORT=1025

データベース初期化スクリプト

-- docker/db/init.sql
-- この SQL は PostgreSQL コンテナの初回起動時に自動実行される

-- 拡張機能の有効化
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- テスト用データベースの作成
CREATE DATABASE mydb_test;

本番環境向けの構成

開発環境と本番環境では求められる設定が異なります。

マルチステージビルドの本番用Dockerfile

# Dockerfile.prod
# ===== Stage 1: 依存関係のインストール =====
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# ===== Stage 2: ビルド =====
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

# Prismaクライアントの生成
RUN npx prisma generate

# Next.jsのビルド
RUN npm run build

# ===== Stage 3: 本番イメージ =====
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# セキュリティ:root以外のユーザーで実行
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 必要なファイルだけコピー
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /app/prisma ./prisma

USER nextjs
EXPOSE 3000
ENV PORT=3000

CMD ["node", "server.js"]

本番用のcompose.prod.yml

# compose.prod.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.prod
    ports:
      - "3000:3000"
    env_file:
      - .env.production
    environment:
      - NODE_ENV=production
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: always
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'

  db:
    image: postgres:16-alpine
    env_file:
      - .env.production
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: always
    deploy:
      resources:
        limits:
          memory: 1G

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    command: >
      redis-server
      --appendonly yes
      --maxmemory 128mb
      --maxmemory-policy allkeys-lru
      --requirepass ${REDIS_PASSWORD}
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: always

volumes:
  pgdata:
  redis-data:
# 本番用のデプロイコマンド
docker compose -f compose.prod.yml up -d --build

よくある構成パターン

フロントエンド + バックエンドの分離

# compose.yml
services:
  frontend:
    build:
      context: ./frontend
    ports:
      - "3000:3000"
    environment:
      - NEXT_PUBLIC_API_URL=http://localhost:8000
    volumes:
      - ./frontend:/app
      - /app/node_modules

  backend:
    build:
      context: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/mydb
    depends_on:
      - db
    volumes:
      - ./backend:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Nginxリバースプロキシ構成

# compose.yml
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./docker/nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    restart: always

  app:
    build: .
    expose:
      - "3000"    # portsではなくexposeで内部ネットワークのみに公開
    environment:
      - NODE_ENV=production
    restart: always
# docker/nginx/nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream app {
        server app:3000;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

トラブルシューティングとベストプラクティス

よくあるトラブルと対処法

コンテナが起動しない

# ログを確認
docker compose logs app

# コンテナの状態を確認
docker compose ps -a

# ビルドキャッシュをクリアして再ビルド
docker compose build --no-cache
docker compose up -d

ポートが既に使用されている

# 使用中のポートを確認(macOS/Linux)
lsof -i :3000

# 別のポートにマッピング
ports:
  - "3001:3000"   # ホストの3001番ポートをコンテナの3000番に

ボリュームのパーミッション問題

# Linuxでのファイルオーナー問題
# Dockerfileでユーザーを指定
RUN chown -R node:node /app
USER node

ベストプラクティス

1. healthcheckを必ず設定する

db:
  image: postgres:16-alpine
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]
    interval: 5s
    timeout: 5s
    retries: 5
    start_period: 10s   # 初回チェックまでの待機時間

2. depends_onにconditionを使う

app:
  depends_on:
    db:
      condition: service_healthy   # DBが起動完了してから起動
    redis:
      condition: service_healthy

3. .dockerignoreを設定する

# .dockerignore
node_modules
.next
.git
.env
.env.local
*.md
docker-compose*.yml

4. プロファイルで開発ツールを分離する

services:
  pgadmin:
    image: dpage/pgadmin4
    profiles:
      - tools

  redis-commander:
    image: rediscommander/redis-commander
    profiles:
      - tools
# 通常の起動(開発ツールは起動しない)
docker compose up -d

# 開発ツールも含めて起動
docker compose --profile tools up -d

5. ボリュームの使い分け

volumes:
  # Named Volume:永続化が必要なデータ(DBデータなど)
  pgdata:

  # Bind Mount:開発時のソースコード同期
  # volumes:
  #   - .:/app

  # Anonymous Volume:コンテナ内のパッケージを保護
  # volumes:
  #   - /app/node_modules

まとめ

Docker Composeを使った開発環境構築のポイントを整理します。

  • compose.ymlで複数サービスを宣言的に管理し、docker compose up -dで一発起動
  • healthcheck + depends_onでサービスの起動順序を制御
  • 環境変数.envファイルで管理し、.env.exampleをリポジトリにコミット
  • マルチステージビルドで本番イメージを最小化
  • profilesで開発ツール(pgAdmin等)を分離
  • 開発と本番は別のcomposeファイルで管理

Docker Composeを導入すれば、「READMEに書いてある手順通りにやったのに動かない」という問題がなくなります。新しいメンバーが参加しても、git cloneしてdocker compose up -dするだけで開発を始められる環境を目指しましょう。

#Docker#Docker Compose#開発環境
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

AI活用のヒントをお探しですか?お気軽にご相談ください。

まずは話だけ聞いてもらう