Docker Compose本番環境デプロイ設定のベストプラクティス完全ガイド
Docker Compose v5対応。セキュリティ、ヘルスチェック、リソース制限、ログ管理など本番環境に必要な設定をコード例付きで解説します。
Docker Composeの本番環境デプロイには、開発環境とは異なるベストプラクティスが必要です。セキュリティ、可用性、パフォーマンスの3軸で設定を見直さないと、情報漏洩やサービス停止のリスクが生まれます。たとえば、環境変数に直書きしたDBパスワードは docker inspect で誰でも読めてしまいますし、リソース制限のないコンテナは1つの暴走でホスト全体を巻き込みます。本記事では、Nginx + App + PostgreSQL + Redisの4コンテナ構成を題材に、環境分離・Secrets管理・ヘルスチェック・リソース制限・イメージ軽量化・ログ運用・負荷分散・トラブルシューティングまでを、コピペで使える設定例とともに解説します。
前提条件と本番環境で求められる設定要件
Docker Composeで本番デプロイを行う前に、ツールのバージョンと環境差分を把握しておく必要があります。ここでは前提条件と、本記事で扱うサンプル構成を整理します。
必要なツールとバージョン(Docker Compose v5.0.2)
本記事の内容は2026年2月時点の環境に基づいています。以下のバージョンで動作を確認しています。
| ツール | バージョン | 備考 |
|---|---|---|
| Docker Engine | 27.x 以上 | docker version で確認 |
| Docker Compose | v5.0.2 | Docker Desktop 4.60.1 に同梱 |
| Docker Desktop | 4.60.1 | macOS / Windows の場合 |
バージョンの確認コマンドは以下のとおりです。
# macOS / Linux
docker --version && docker compose version
# Windows PowerShell
docker --version; docker compose version
出力例です。
Docker version 27.5.1, build 9f9e405
Docker Compose version v5.0.2
(出典: GitHub docker/compose Releases)
開発環境と本番環境の設定差分を理解する
開発環境の docker-compose.yml をそのまま本番に持ち込むのは危険です。主な差分を表にまとめます。
| 設定項目 | 開発環境 | 本番環境 |
|---|---|---|
| ボリューム | ホストのソースコードをバインドマウント | コードはイメージ内に含め、外部マウントしない |
| 認証情報 | .env ファイルで管理 | Docker Secrets や外部のシークレット管理サービスを使用 |
| 再起動ポリシー | 未設定(手動再起動) | unless-stopped で自動復旧 |
| リソース制限 | 制限なし | CPU・メモリの上限を明示的に設定 |
| ログ管理 | デフォルト(json-file) | Fluentd 等の集中ログ管理 |
| イメージタグ | latest でも許容 | 明示的なバージョン指定(例: nginx:1.27.3) |
Docker公式ドキュメントでも、本番環境ではアプリケーションコードのボリュームバインディングを削除し、コードをイメージ内部に閉じ込めるよう推奨しています(出典: Docker公式 - 本番環境でのComposeの利用)。
本記事で扱うサンプル構成(Nginx + App + PostgreSQL + Redis)
以降のセクションでは、次の4コンテナ構成を例に解説を進めます。
[クライアント]
↓
[Nginx (リバースプロキシ)] :80
↓
[App (Node.js)] :3000
↓ ↓
[PostgreSQL] [Redis]
:5432 :6379
この構成は、Webアプリケーションの典型的なパターンです。Nginxがリクエストを受け、Appコンテナへ振り分けます。データの永続化にはPostgreSQLを、セッションやキャッシュにはRedisを使います。各セクションでこの構成に対して、セキュリティ・信頼性・パフォーマンスの設定を順に追加していきます。
手順1――複数Composeファイルによる環境分離とセキュリティ設定
本番デプロイで最初にやるべきは「環境の分離」と「機密情報の保護」です。開発用の設定をそのまま本番に持ち込むと、ポートの露出やデバッグツールの混入といったリスクが生まれます。ここでは3つの観点から設定を固めていきます。
docker-compose.ymlとproduction.ymlの分離戦略
Docker公式ドキュメントでは、本番用の変更点だけを別ファイルに切り出す方法を推奨しています(Docker公式 - Composeを本番環境で使う)。ベースの docker-compose.yml に共通設定を書き、production.yml で上書きする構成です。
以下がベースファイルの例です。
# docker-compose.yml(共通設定)
services:
app:
build: .
environment:
- NODE_ENV=production
db:
image: postgres:16.2
本番用のオーバーライドファイルはこう書きます。
# production.yml(本番のみの差分)
services:
app:
restart: unless-stopped
ports:
- "80:3000"
db:
restart: unless-stopped
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
実行時は -f オプションで両ファイルをマージします。
# macOS / Linux
docker compose -f docker-compose.yml -f production.yml up -d
# Windows PowerShell
docker compose -f docker-compose.yml -f production.yml up -d
こうすると開発環境には不要な再起動ポリシーやボリュームを持ち込まずに済みます。
Docker Secretsで認証情報を安全に管理する
環境変数にパスワードを直書きする方法は、docker inspect で丸見えになるため本番には不向きです。Docker Secretsを使えば、機密情報はコンテナ内の /run/secrets/<名前> にファイルとしてマウントされ、環境変数には露出しません(Docker公式 - Use secrets in Compose)。
# production.yml にSecrets設定を追加
services:
db:
image: postgres:16.2
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
secrets/db_password.txt にパスワードを1行だけ記載しておきます。アプリケーション側は環境変数ではなくファイルから読み取る実装に変更してください。
.envファイルのGit除外とボリュームバインド削除
.env ファイルをうっかりコミットする事故は後を絶ちません。.gitignore に以下を追加し、確実に除外します。
.env
.env.*
secrets/
もうひとつ見落としがちなのが、開発用のボリュームバインドです。./src:/app/src のようなホストマウントを本番に残すと、外部からコードを書き換えられるリスクがあります(Docker公式 - 本番環境でのComposeの利用)。本番ではビルド済みのコードをイメージ内に閉じ込め、ボリュームバインドは永続データ用のnamed volumeだけに限定してください。
手順2――ヘルスチェック・再起動ポリシー・リソース制限の設定
コンテナが「起動しているが応答しない」状態は、本番環境で最も厄介な障害パターンです。ヘルスチェック・再起動ポリシー・リソース制限の3つを組み合わせることで、自己回復する堅牢なシステムを構築できます。
healthcheckパラメータとDB別の実装例(PostgreSQL・Redis)
ヘルスチェックは、コンテナの「生存確認」を自動化する仕組みです。主要パラメータは test・interval・timeout・retries・start_period の5つ。終了ステータス0が正常、それ以外は異常と判定されます(Docker公式ドキュメント)。
PostgreSQLには pg_isready、Redisには redis-cli ping が推奨されています(Last9)。以下に実装例を示します。
# docker-compose.yml(Docker Compose v5.0.2対応)
services:
postgres:
image: postgres:16.2
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:7.2
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
start_period はコンテナ起動直後の猶予期間です。PostgreSQLのように初期化に時間がかかるサービスには、30秒程度を設定しておくと誤検知を防げます。
restart_policyの選択(unless-stopped推奨)
本番環境では unless-stopped が最も汎用的な再起動ポリシーです。クラッシュ時に自動復旧しつつ、docker stop による手動停止は維持します(Docker公式ドキュメント)。
services:
app:
image: myapp:1.2.0
restart: unless-stopped
always との違いは、手動停止後の挙動にあります。always はDockerデーモン再起動時にも再起動しますが、メンテナンス中に意図せずコンテナが立ち上がるリスクがあります。unless-stopped なら手動介入を尊重してくれます。
deploy.resourcesによるCPU・メモリ制限の設定
リソース制限を設定しないと、1つのコンテナが暴走してホスト全体がOOM(Out of Memory)に陥る危険があります(Docker公式ドキュメント)。
services:
app:
image: myapp:1.2.0
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
limits は上限値、reservations はスケジューリング時の最低保証値です。
注意点が1つあります。 deploy キー配下の設定は通常の docker compose up では無視されます(GitHub Issue #6539)。Swarmモードでは docker stack deploy を使用してください。非Swarm環境でメモリ制限をかけるには、mem_limit や cpus をサービス直下に記述する方法もあります。
リソース制限値は、開発環境の値をそのまま使わず、本番相当の負荷テストを実施して決定してください。
手順3――マルチステージビルド・ログ管理・Nginxリバースプロキシ
イメージの軽量化、ログの集約、トラフィックの分散。本番環境の品質を左右するこの3要素を設定していきます。
マルチステージビルドでイメージを96%軽量化する
マルチステージビルドは、ビルド用と実行用のステージを分離する手法です。開発ツールやソースコードを最終イメージに含めません。これによりイメージサイズを大幅に削減できます。通常729MBのイメージが26.2MBまで縮小した事例もあります(Zenn - Dockerのマルチステージビルド)。
以下はNode.jsアプリの例です。
# ステージ1: ビルド環境
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# ステージ2: 実行環境(ビルドツールを含まない)
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]
ポイントは最終ステージで USER node を指定することです。root権限での実行を避け、セキュリティリスクを低減できます。また、イメージタグは node:20-alpine のように明示的にバージョンを指定してください。latest タグは本番環境で非決定的な挙動を招きます(Bunnyshell)。
Fluentdログドライバーによる集中ログ管理
デフォルトの json-file ドライバーはログをディスクに書き込み続けます。放置するとディスク容量を圧迫する原因に。Fluentdドライバーを使えば、ログをネットワーク経由で転送するため、コンテナホストのディスク問題を回避できます(Docker公式ドキュメント)。
production.yml に以下を追記してください。
services:
app:
logging:
driver: fluentd
options:
fluentd-address: "localhost:24224"
tag: "docker.{{.Name}}"
fluentd-async: "true"
fluentd-async: "true" を設定すると、Fluentdへの送信を非同期で処理します。Fluentd側の障害がアプリケーションをブロックしません。
Nginxの負荷分散アルゴリズムとupstream設定
Nginxをリバースプロキシとして配置し、複数のアプリコンテナにトラフィックを分散します。利用できるアルゴリズムは以下の通りです(OneUpTime)。
| アルゴリズム | 特徴 | 適するケース |
|---|---|---|
round-robin | 順番に振り分け(デフォルト) | 均一なリクエスト処理 |
least_conn | 接続数が最少のサーバーへ | 処理時間にばらつきがある場合 |
ip_hash | 同一IPを同一サーバーへ | セッション維持が必要な場合 |
nginx.conf の設定例です。
upstream app_backend {
least_conn;
server app:3000;
server app:3001;
}
server {
listen 80;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
docker-compose.yml では、Nginxコンテナがアプリより後に起動するよう depends_on を設定します。
services:
nginx:
image: nginx:1.27.3-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
app:
condition: service_healthy
:ro(read-only)でマウントし、設定ファイルの改ざんを防止するのが本番環境でのポイントです。
手順4――デプロイ戦略とCI/CDパイプライン統合
本番環境の安定運用では、どうビルドするかとどう切り替えるかが鍵です。イメージタグの管理とゼロダウンタイムデプロイの2点を押さえましょう。
イメージタグの明示的バージョン指定とlatestの罠
latestタグは「最新」を意味しません。単にタグ未指定時のデフォルト名にすぎず、どのバージョンが入っているか保証がないのです。本番環境でlatestを使うと、再ビルドのたびに異なるイメージが取得され、予測不能な挙動を招きます(Bunnyshell)。
production.ymlでイメージタグを明示的に固定しましょう。
# production.yml — バージョンを明示的に指定
services:
web:
image: myapp/web:1.4.2
db:
image: postgres:16.2-alpine
cache:
image: redis:7.2.4-alpine
タグにはセマンティックバージョニングかGitコミットハッシュを使います。CI/CDパイプラインでビルド時にタグを付与する例を示します。
# macOS / Linux — CIでのイメージビルドとプッシュ
IMAGE_TAG=$(git rev-parse --short HEAD)
docker compose -f docker-compose.yml -f production.yml build
docker tag myapp/web:latest myapp/web:${IMAGE_TAG}
docker push myapp/web:${IMAGE_TAG}
# Windows PowerShell — CIでのイメージビルドとプッシュ
$IMAGE_TAG = git rev-parse --short HEAD
docker compose -f docker-compose.yml -f production.yml build
docker tag myapp/web:latest myapp/web:$IMAGE_TAG
docker push myapp/web:$IMAGE_TAG
この運用なら、問題発生時に前バージョンへ即座にロールバックできます。
ゼロダウンタイムデプロイの実現方法
Docker Compose単体にはローリングアップデート機能がありません。ただし、Nginxリバースプロキシと--no-depsフラグを組み合わせると、ダウンタイムを最小化できます。
手順は3ステップです。
1. 新バージョンのコンテナを追加起動します。
# macOS / Linux — 既存コンテナを止めずに新コンテナを起動
docker compose -f docker-compose.yml -f production.yml up -d --no-deps --scale web=2 web
# Windows PowerShell
docker compose -f docker-compose.yml -f production.yml up -d --no-deps --scale web=2 web
--no-depsは依存サービス(DBやキャッシュ)の再作成を防ぎます。--scale web=2で新旧2台が同時に稼働する状態を作ります。
2. ヘルスチェックで新コンテナの起動を確認します。
# 新コンテナがhealthyになるまで待機
docker compose ps web
3. 旧コンテナを停止し、スケールを1に戻します。
# macOS / Linux
docker compose -f docker-compose.yml -f production.yml up -d --no-deps --scale web=1 web
# Windows PowerShell
docker compose -f docker-compose.yml -f production.yml up -d --no-deps --scale web=1 web
Nginxのupstream設定でコンテナ名をDNSベースで解決していれば、スケール変更時に自動的にトラフィックが振り分けられます。
注意: この方法は単一サーバー上の簡易的なゼロダウンタイム戦略です。複数サーバーをまたぐ大規模デプロイには、Docker SwarmやKubernetesの導入を検討してください。
トラブルシューティング:本番環境で頻出するエラーと対処法
本番デプロイ時に遭遇しやすいエラーを3パターンに絞って解説します。いずれも筆者自身が実務で経験した頻出ケースです。
ポート競合・YAML構文エラー・コンテナ即時終了
ポート競合は port already allocated というメッセージで発生します。ホスト側で既にポートが使用されている状態です。以下のコマンドで原因を特定できます。
# macOS / Linux:ポート80を使用中のプロセスを確認
sudo lsof -i :80
# Windows PowerShell:ポート80を使用中のプロセスを確認
netstat -ano | findstr :80
YAML構文エラーはデプロイ失敗の最頻出原因です。スペース1つのズレでファイル全体が壊れます(AST Consulting)。デプロイ前に必ず検証してください。
# macOS / Linux:Composeファイルの構文チェック
docker compose config --quiet
# Windows PowerShell:Composeファイルの構文チェック
docker compose config --quiet
コンテナ即時終了は、ログの確認が最優先です。docker compose logs <サービス名> でエラー内容を特定してください。依存サービスの起動順序やヘルスチェック設定の見直しが有効です。
CPUアーキテクチャ非互換(arm64 vs amd64)の解決
Apple Silicon(M1/M2/M3)Macでビルドしたイメージは、arm64アーキテクチャです。本番サーバーがamd64の場合、そのままでは動作しません(Qiita)。
compose.yml の build セクションで対象プラットフォームを指定します。
# compose.yml(抜粋)
services:
app:
build:
context: .
dockerfile: Dockerfile
platforms:
- linux/amd64
CI/CD環境でビルドする場合は、コマンドで明示します。
# macOS / Linux:amd64向けにビルド
docker compose build --no-cache
開発マシンと本番のアーキテクチャが異なるチームでは、CI上でのビルドに統一するのが確実です。
deployキーがdocker-compose upで無視される問題
deploy キー配下の resources(CPU・メモリ制限)や replicas は、通常の docker compose up では無視されます。これはDocker Compose v5.0.2(2026年1月時点の最新版)でも同様の仕様です(GitHub Issue #6539)。
対処法は2つあります。
方法1:Docker Swarmモードを使う場合。
# macOS / Linux:Swarmモードでデプロイ
docker stack deploy -c compose.yml myapp
# Windows PowerShell:Swarmモードでデプロイ
docker stack deploy -c compose.yml myapp
方法2:Swarmを使わずリソース制限する場合。 docker compose run や各サービスの mem_limit・cpus を直接指定します。
# compose.yml(Swarm不使用時のリソース制限)
services:
app:
image: myapp:1.2.0
mem_limit: 512m
cpus: "0.5"
チームの運用規模に応じて、どちらの方法を採用するか判断してください。Swarmモードは小〜中規模の本番環境で十分に機能します。
まとめ:本番デプロイ前に確認すべきチェックリストと次のステップ
本番環境へのデプロイ前に、以下のチェックリストで設定漏れがないか確認してください。
セキュリティチェックポイント
- 認証情報はDocker Secretsまたは外部シークレット管理サービスで保護しているか
.envファイルが.gitignoreに含まれているか- アプリケーションコードのボリュームバインドを削除し、イメージ内に閉じ込めているか
- イメージタグに
latestではなく明示的なバージョンを指定しているか - マルチステージビルドでビルドツールやソースコードを最終イメージから除外しているか
可用性チェックポイント
restart: unless-stoppedを全サービスに設定しているか- 各サービスにヘルスチェック(
healthcheck)を定義しているか depends_onにcondition: service_healthyを指定して起動順序を制御しているか- YAML構文を
docker compose configで検証しているか
パフォーマンスチェックポイント
- CPU・メモリのリソース制限(
deploy.resources.limits)を設定しているか - ログドライバーに
max-sizeとmax-fileを指定し、ディスク溢れを防いでいるか - 本番に近い負荷でリソース制限値を検証しているか
Docker Composeの先にあるもの
Docker Composeは単一サーバーでの運用が前提のツールです。以下のような状況が出てきたら、KubernetesやDocker Swarmへの移行を検討する段階に入っています。
- 複数サーバーにまたがるスケールアウトが必要になった
- ゼロダウンタイムのローリングアップデートを自動化したい
- サービスメッシュやオートスケーリングが求められている
筆者の経験では、月間100万PV程度まではDocker Compose + Nginx構成で十分に対応できます。まずは本記事のチェックリストを現在の docker-compose.yml に適用し、1つずつ設定を固めるところから始めてみてください。CI/CDパイプライン(GitHub ActionsやGitLab CI)との統合は、この基盤が整った後の自然な次のステップになります。
参考文献
- https://github.com/docker/compose/releases
- https://docs.docker.jp/compose/production.html
- https://docs.docker.com/compose/how-tos/use-secrets/
- https://docs.docker.com/engine/swarm/secrets/
- https://docs.docker.com/reference/compose-file/deploy/
- https://docs.docker.com/reference/compose-file/services/
- https://last9.io/blog/docker-compose-health-checks/
- https://docs.docker.com/engine/containers/resource_constraints/
- https://docs.docker.com/engine/logging/drivers/fluentd/
- https://zenn.dev/hakshu/articles/docker-multi-stage-build
- https://www.bunnyshell.com/blog/is-docker-compose-production-ready/
- https://oneuptime.com/blog/post/2026-01-16-docker-nginx-reverse-proxy/view
- https://eastondev.com/blog/en/posts/dev/20251217-docker-compose-troubleshooting/
- https://astconsulting.in/docker/solve-docker-compose-errors-troubleshooting-guide
- https://qiita.com/timeless-residents/items/54a6bceb454aeb1a4fdd
- https://qiita.com/muff1225/items/4edea7b039dd9f26098f
- https://matsuand.github.io/docs.docker.jp.onthefly/compose/production/
- https://github.com/docker/compose/issues/6539