Docker Composeは、複数コンテナを1つのYAMLで宣言的に定義し、同じネットワーク上で協調動作させるオーケストレーションツールです。Web(Nginx)+アプリ(PHP/Node.js/Python)+DB(MySQL/PostgreSQL)+キャッシュ(Redis)+メール(mailpit)という典型的なWebサービス構成がdocker compose up -d一発で再現できるため、2026年現在のローカル開発環境の事実上の標準です。
多くの入門記事は「services:に3〜4つ並べて起動」までで止まっていますが、実務で差がつくのはサービス間通信のDNS/healthcheck+depends_on conditionでの起動順序制御/Compose Watchによるファイル変更自動反映/profilesの環境別切替/compose.override.ymlでの開発・本番分離/複数ネットワーク設計/secrets/resource limitsといった運用寄りトピックです。これらを押さえるかどうかで、Composeが「動くだけ」か「チーム共有できる基盤」かが決まります。
この記事では、Compose V2(2023年にV1が廃止)の最新仕様に沿って、基礎から運用レベルまで段階的に解説します。Nginx特化は【Docker】Nginxローカル開発環境構築完全ガイド、PHP-FPM構成は【Docker】Nginx + PHP-FPM完全ガイド、WordPress特化は【Docker】WordPress環境構築完全ガイド、永続化は【Docker】volumeの使い方とデータ永続化の基本/【Docker】bind mountとvolumeの違いと使い分けで補完できます。
この記事で学べること
- Compose V2の最新仕様(
version:撤廃/docker composeコマンド) - サービス間通信のメカニズム(内部DNS/service discovery/aliases)
- healthcheckの完全設定(test/interval/start_period/start_interval)
depends_onのconditionで起動順序を保証(service_started/service_healthy/service_completed_successfully)- 複数ネットワーク設計(frontend/backend分離で攻撃面を減らす)
- 環境変数/
env_file/secretsの使い分け - Compose Watchでホスト変更をコンテナへ自動同期(2024年追加)
- profilesで条件付きサービス(dev-tools/testing等)
compose.yml+compose.override.yml+compose.prod.ymlのオーバーライド運用- restart policy/resource limits/logging driverで安定化
docker compose configでマージ結果を検証する診断技- Compose→Kubernetes移行(
kompose convert)の視点
30秒クイックスタート:5サービス連携の最小例
Web(Nginx)+アプリ(PHP-FPM)+DB(MariaDB)+Cache(Redis)+Mail(mailpit)を一気に立ち上げる最小構成です。
services:
web:
image: nginx:1.27-alpine
ports: ["8080:80"]
volumes:
- ./app:/var/www/html
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
app:
condition: service_healthy
networks: [frontend, backend]
app:
image: php:8.3-fpm-alpine
volumes:
- ./app:/var/www/html
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 10s
retries: 5
networks: [backend]
db:
image: mariadb:11
environment:
MARIADB_DATABASE: app
MARIADB_USER: app
MARIADB_PASSWORD: app
MARIADB_ROOT_PASSWORD: root
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
retries: 10
networks: [backend]
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
networks: [backend]
mail:
image: axllent/mailpit:latest
ports:
- "8025:8025" # Web UI
networks: [backend]
volumes:
db_data:
networks:
frontend:
backend:
docker compose up -d # 全サービスが順序どおり起動(healthcheck経由でappはDB待機、webはapp待機) # http://localhost:8080 でWeb # http://localhost:8025 でMailpit UI # サービス状態確認 docker compose ps # healthy/healthy/healthy/... と並ぶ # 停止 docker compose down # データごと削除したい場合 docker compose down -v
2026年の推奨ポイント:version:キーは不要(Compose V2以降は警告が出る)。docker-compose(V1、ハイフン付き)は廃止、docker compose(スペース区切り、V2)を使う。depends_onは必ずcondition: service_healthy等を指定して「起動順序ではなく準備完了順序」を保証するのが現代的な運用。
Compose V2の全体像と2026年仕様
Docker Composeは2023年にV1が廃止され、Goで書き直されたV2(docker composeサブコマンド)が標準です。さらに「Compose Spec」として仕様がOSS化され、Docker Desktop以外のツール(Nerdctl/Podman Compose等)も同じYAMLを共有できるようになりました。
主要な変更点(V1→V2)
# 1. コマンドを置き換え # docker-compose up -d → docker compose up -d # 2. version: キー削除 # sed -i '/^version:/d' compose.yml # 3. ファイル名を短縮形にリネーム(任意) mv docker-compose.yml compose.yml # 4. 検証 docker compose config # マージ済みの最終設定を表示 docker compose config --services
サービス間通信のメカニズム
Composeが生成するデフォルトネットワーク(プロジェクト名_default)は内部DNSサービス付きで、サービス名をそのままホスト名として解決できます。IPアドレス直書きは不要です。
DNSベースのservice discovery
# PHP (PDO)
$pdo = new PDO(
"mysql:host=db;dbname=app;charset=utf8mb4",
"app", "app"
);
# ↑ "db" がComposeのサービス名として自動解決される
# Node.js
const client = new Redis({ host: "redis", port: 6379 });
# Python
conn = psycopg2.connect(host="db", user="app", password="app", dbname="app")
ホスト名エイリアス(aliases)
services:
db:
image: mariadb:11
networks:
backend:
aliases:
- mysql # db/mysql/primary.dbで解決
- primary.db
networks:
backend:
複数ネットワーク設計(frontend/backend分離)
攻撃面を減らす定番パターン。公開サービス(Nginx)だけfrontendに所属させ、DBや内部APIはbackendだけに閉じる。
services:
web:
image: nginx:1.27-alpine
ports: ["8080:80"] # 外部公開
networks: [frontend, backend] # 両方に所属
app:
build: ./app
networks: [backend] # 内部のみ
db:
image: mariadb:11
networks: [backend] # 内部のみ
networks:
frontend:
backend:
internal: true # 外部接続不可(インターネット遮断)
backend: internal: trueを設定すると、そのネットワークに所属するコンテナはインターネットへ出れません。DBなどバックエンドの流出経路を物理的に遮断でき、セキュリティ事故への最終防壁になります。
extra_hosts でホスト側にアクセス
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway" # Linuxでも動作
- "api.example.com:127.0.0.1" # /etc/hosts風に追加
healthcheck + depends_on conditionで起動順序を保証
「depends_on: [db]だけではDBコンテナの起動完了は待つが準備完了までは待たない」——これが「Error establishing database connection」の定番原因です。Compose V2ではconditionで準備完了を厳密に待機できます。
conditionの3種
services:
# MariaDB(healthcheck.shが同梱)
db:
image: mariadb:11
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s # 起動直後はチェック失敗を許容
# PostgreSQL(pg_isready)
postgres:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 5s
retries: 10
# Redis
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
# HTTP(curl/wget)
web:
image: nginx:1.27-alpine
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost/healthz || exit 1"]
interval: 10s
retries: 5
マイグレーション完了を待ってから本体起動
services:
db:
image: mariadb:11
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect"]
interval: 5s
retries: 10
migrate:
image: app-migrate:latest
depends_on:
db: { condition: service_healthy }
command: ["./migrate.sh"]
restart: "no" # ワンショット
app:
image: app:latest
depends_on:
db: { condition: service_healthy }
migrate: { condition: service_completed_successfully }
healthcheck設計のコツ:①start_periodで起動直後のfailを無視(重いDBは30〜60秒推奨)、②testはCMD配列形式(shellエスケープ不要)かCMD-SHELL、③環境変数は$$でエスケープ($だとホスト展開される)、④retries× interval で最大待機時間を見積もり。
環境変数・env_file・secretsの使い分け
COMPOSE_PROJECT_NAME=myapp DB_HOST=db DB_USER=app DB_PASSWORD=localdev APP_ENV=development
services:
app:
environment:
# ${VAR}でホストの.envから展開
DB_HOST: ${DB_HOST}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
APP_ENV: ${APP_ENV:-production} # デフォルト値指定
# もしくはenv_fileで一括
env_file:
- .env
- .env.local
services:
db:
image: mariadb:11
environment:
MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_root
# ↑ _FILE サフィックスでファイル読み込み指定
secrets:
- db_root
app:
image: app:latest
secrets:
- api_key
# アプリ内では /run/secrets/api_key を読む
secrets:
db_root:
file: ./secrets/db_root.txt # .gitignore対象
api_key:
environment: API_KEY # ホストの$API_KEYを参照
機密情報をenvironment:に直書きしない。docker inspectで全員が閲覧でき、プロセスリスト(/proc/<pid>/environ)からも読めます。secrets:は/run/secrets/<name>にファイルマウントされるためプロセス環境変数には載らず、流出リスクが激減。
Compose Watch:ホスト変更を自動でコンテナへ反映
Docker Compose 2.22(2024年)で正式追加されたdocker compose watchは、ホストのファイル変更を検知してコンテナ内へ即同期/再ビルドする新機能。従来の巨大なbind mountによるパフォーマンス問題を解決します。
services:
app:
build: ./app
develop:
watch:
# ① ホスト→コンテナへファイル同期(再ビルドなし)
- action: sync
path: ./app/src
target: /app/src
ignore:
- "**/node_modules"
- "**/.git"
# ② package.jsonが変わったら再ビルド
- action: rebuild
path: ./app/package.json
# ③ 同期+再起動
- action: sync+restart
path: ./app/nginx.conf
target: /etc/nginx/nginx.conf
docker compose watch # → ホスト変更を監視し、各actionを自動実行 # → ./app/src/*.js を編集するとコンテナ内に即反映 # → package.json変更で自動リビルド # バックグラウンド実行 docker compose up --watch -d
Watch vs bind mountの違い
- bind mountは常時監視でWindows/macOSで遅いことがある
- Watchは変更イベント駆動で軽量、大規模プロジェクトでも速い
- Watchならnode_modulesを除外してコンテナ側で完結させられる
- rebuild actionで依存ファイル変更に自動追従できる
profiles:環境別の条件付きサービス
profiles:でサービスをグループ化し、デフォルトでは起動しないようにできます。開発ツール(phpMyAdmin)/テスト用サービス/デバッグ支援などを必要時のみ立ち上げる定石。
services:
# デフォルト起動(profilesなし)
app:
image: app:latest
db:
image: mariadb:11
# dev-tools profileでのみ起動
phpmyadmin:
image: phpmyadmin:latest
ports: ["8081:80"]
environment:
PMA_HOST: db
profiles: ["dev-tools"]
# testing profileでのみ起動
test-runner:
image: app-test:latest
profiles: ["testing"]
# monitoring profile
prometheus:
image: prom/prometheus:latest
profiles: ["monitoring"]
grafana:
image: grafana/grafana:latest
profiles: ["monitoring"]
# デフォルト(profilesなしサービスのみ) docker compose up -d # → app, dbだけ起動 # 特定profile有効化 docker compose --profile dev-tools up -d # → app, db, phpmyadmin # 複数profile docker compose --profile dev-tools --profile monitoring up -d # → app, db, phpmyadmin, prometheus, grafana # 環境変数でも指定可 COMPOSE_PROFILES=dev-tools,monitoring docker compose up -d
profilesの推奨グループ:①dev-tools(phpmyadmin/adminer/mailpit/redis-commander)、②testing(test-runner/fixture-loader)、③monitoring(prometheus/grafana/jaeger)、④cli(wpcli/rails-console/manage.py)。必要な時だけ立ち上げメモリを節約できます。
Compose Override:開発と本番の設定を分離
Composeは複数YAMLを重ねてマージできます。共通設定をcompose.ymlに、環境別をcompose.override.yml/compose.prod.ymlに分けるのが実務標準。
services:
app:
image: app:latest
networks: [app-net]
db:
image: mariadb:11
networks: [app-net]
networks:
app-net:
# デフォルトで自動マージされるファイル
services:
app:
build: ./app
volumes:
- ./app:/var/www/html # 開発中はbind mount
environment:
APP_ENV: development
XDEBUG_MODE: debug
db:
ports: ["3306:3306"] # 開発ではホストにも公開
adminer:
image: adminer:latest
ports: ["8081:8080"]
networks: [app-net]
services:
app:
image: ghcr.io/myorg/app:v1.2.3 # 本番はbuildではなくimage
restart: always
environment:
APP_ENV: production
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
db:
volumes:
- /mnt/nas/db-data:/var/lib/mysql # 本番は専用ストレージ
restart: always
deploy:
resources:
limits:
memory: 2G
# 開発(override.ymlが自動マージ) docker compose up -d # 本番(-f で明示指定) docker compose -f compose.yml -f compose.prod.yml up -d # マージ結果を確認(デバッグ時に便利) docker compose -f compose.yml -f compose.prod.yml config
COMPOSE_FILE環境変数
毎回-fを打つのが面倒ならCOMPOSE_FILE=compose.yml:compose.prod.ymlを.envに書く。またはMakefile/Taskfileにmake prod等のタスク化するのが実戦的。
restart・resource limit・loggingで安定運用
restart policy
services:
app:
# "no"(既定):再起動しない
restart: "no"
# always:常に再起動(手動stopを除く)
restart: always
# unless-stopped:手動stopまでは再起動(推奨)
restart: unless-stopped
# on-failure[:max-retries]:失敗時のみ
restart: on-failure:3
resource limits(deploy.resources)
services:
app:
image: app:latest
deploy:
resources:
limits:
cpus: "1.5" # 1.5コアまで
memory: 512M
reservations:
cpus: "0.5" # 最低0.5コア予約
memory: 256M
# Compose V2では mem_limit / cpus ショートカットも可
# mem_limit: 512m
# cpus: 1.5
deploy.resources.limitsは元々Swarm向けの構文ですが、Compose V2ではローカル実行でも適用されます。DBやRedisはメモリ制限で突然OOM killされないよう余裕持って設定し、limit未設定だとホストメモリ枯渇→他サービスも道連れで停止するリスクがあります。
logging driver
services:
app:
logging:
driver: json-file
options:
max-size: "10m" # 1ファイル最大サイズ
max-file: "3" # ローテーション数
# 計30MBまでログ保持
# 本番ではJournald/fluentd/awslogs等も
app_prod:
logging:
driver: awslogs
options:
awslogs-group: /ecs/app
awslogs-region: ap-northeast-1
init / read_only / tmpfs(安定化)
services:
app:
init: true # tini をPID 1 に(SIGTERM伝播・ゾンビ防止)
read_only: true # ルートFSを読み取り専用
tmpfs:
- /tmp # 書込み許可ディレクトリ
- /var/run
security_opt:
- no-new-privileges:true # 特権昇格禁止
Compose運用のデバッグ術
# 最終マージ結果を表示(override含む) docker compose config # 本番用ファイル込みで確認 docker compose -f compose.yml -f compose.prod.yml config # サービス一覧だけ docker compose config --services # ボリューム一覧 docker compose config --volumes # YAMLを標準化(整形+コメント除去) docker compose config --no-interpolate
# サービス状態(healthy/starting/unhealthy)
docker compose ps
# リアルタイムログ追跡
docker compose logs -f
docker compose logs -f app db # 特定サービスだけ
docker compose logs --tail=100 app
# コンテナ内へ入る
docker compose exec app bash
docker compose exec db mysql -u root -p
# 新規プロセス起動(既存コンテナに対して)
docker compose run --rm app php -v
# healthcheck状態詳細
docker inspect --format="{{json .State.Health}}" $(docker compose ps -q db)
困った時のチェックリスト
docker compose configでマージ後YAMLを確認(override/profilesのミス発見)docker compose psで健全性状態をチェックdocker compose logs <service>でエラー詳細depends_on: condition: service_healthyの未指定が502系事故の王道- ネットワーク切分けミスは
docker compose exec app ping dbで疎通確認 - 環境変数未展開は
${VAR:?err}で明示チェック
よくある質問
docker compose、スペース区切り)に移行してください。記法はほぼ互換ですが、version:キー撤廃/WatchやBuildKit標準化などV2独自の改善を享受できます。localhostはそのコンテナ自身を指します。他サービスに繋ぐにはサービス名をホスト名として使います(例:mysql://db:3306)。Composeの内部DNSが自動解決するため、127.0.0.1やlocalhostをコード内で指定しないのがポイント。depends_on: [db]だけでは「コンテナが起動した」までしか待ちません。DBの準備完了(接続可能状態)まで待つにはcondition: service_healthyとDB側のhealthcheckが必須。本記事「healthcheck + depends_on conditionで起動順序を保証」セクション参照。compose.yml+compose.override.ymlがCompose V2時代のスタンダード。--profile a --profile bと並べるかCOMPOSE_PROFILES=a,b環境変数で指定。deploy.resources/restart: always/logging/secretsを本番向けにチューニングすればOK。kompose convertでcompose.ymlをKubernetes Manifest(Deployment/Service/PVC)に自動変換できます。生成後は手動調整が必要ですが出発点として有用。brew install komposeでインストール→kompose convert -f compose.ymlで実行。COMPOSE_PROJECT_NAMEとして使います。別名にしたい時はdocker compose -p myapp2 upや.envにCOMPOSE_PROJECT_NAME=myapp2を書きます。各プロジェクトに独立したネットワーク/ボリュームが作られ、衝突を避けられます。$$(ドル記号2つ)でエスケープします。例:PostgreSQLのhealthcheckでpg_isready -U $$POSTGRES_USERと書けば、ホスト側では展開されずコンテナ内のシェルで$POSTGRES_USERとして処理されます。image: ghcr.io/myorg/app:v1.2.3で参照するのが定番。GitHub Actionsで自動ビルド&pushするワークフローは【GitHub】Actions完全ガイドを参照。タグバージョン管理は【Git】タグ完全ガイドと組み合わせるのが王道。関連記事
- 【Docker】Nginxローカル開発環境構築完全ガイド — Nginx 6用途別の詳細設定
- 【Docker】Nginx + PHP-FPM完全ガイド — PHP本番相当構成
- 【Docker】WordPress環境構築完全ガイド — WP特化のCompose運用
- 【Docker】MySQL CLI完全ガイド — Compose内DBのコマンド操作
- 【Docker】volumeの使い方とデータ永続化の基本 — named volume基礎
- 【Docker】bind mountとvolumeの違いと使い分け — マウント戦略の判断
- 【保存版】Docker Desktopが「Docker Desktop stopped」で起動できない時の完全解決ガイド — Docker起動トラブル
- 【GitHub】Actions完全ガイド — ComposeイメージのCI/CDビルド
- 【Git】タグ完全ガイド — イメージバージョン管理の参考
まとめ
- Compose V2(
docker compose)が現行標準、version:キーは不要 - サービス間通信はサービス名=内部DNSホスト名で解決(IP不要)
depends_on+condition: service_healthyで準備完了を待機- healthcheckはstart_periodで起動直後のfailを許容、interval × retriesで最大待機
- 複数ネットワーク(frontend/backend)で攻撃面を分離、
internal: trueで外部遮断 - 機密情報は
secrets:を使い、environment:直書きは避ける - Compose Watch(
develop.watch)でホスト変更を軽量に自動同期 - profilesでdev-tools/testing/monitoringを条件付き起動
compose.yml+override.yml+compose.prod.ymlで環境別に分離restart: unless-stopped/deploy.resources/loggingで安定運用init: true/read_only/tmpfsでPID 1問題回避+書込み制限- デバッグ3種の神器:
docker compose config/logs -f/exec bash
Docker Composeは「複数コンテナを連携させる」表面機能だけでなく、healthcheck連鎖・複数ネットワーク分離・profiles条件起動・override環境分離・secrets/resource limitsまで含めたオーケストレーション基盤として本領を発揮します。本記事の完全版compose.ymlとベストプラクティスを土台にすれば、個人開発から中規模本番運用まで一気通貫で設計でき、いずれKubernetesへ移行する場合もkompose convertで出発点にできます。Web(Nginx/PHP-FPM)、DB(MySQL CLI)、CI/CD(GitHub Actions)と組み合わせて運用してください。

