目次
- Docker Composeとは?なぜ必要なのか
- Docker Composeが解決する問題
- Docker Composeのバージョン
- 基本的なcompose.ymlの書き方
- 最小構成の例
- 基本コマンド
- 実践的な開発環境構成
- プロジェクト構成
- 開発用Dockerfile
- 開発用compose.yml
- 環境変数ファイル
- データベース初期化スクリプト
- 本番環境向けの構成
- マルチステージビルドの本番用Dockerfile
- 本番用のcompose.prod.yml
- よくある構成パターン
- フロントエンド + バックエンドの分離
- Nginxリバースプロキシ構成
- トラブルシューティングとベストプラクティス
- よくあるトラブルと対処法
- ベストプラクティス
- まとめ
「新しいメンバーが開発環境を構築するのに丸一日かかった」「自分の環境では動くのに、他のメンバーの環境では動かない」「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するだけで開発を始められる環境を目指しましょう。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します