【Docker】Compose完全ガイド|V2仕様・healthcheck連鎖・複数ネットワーク・Watch・profiles・override・secrets

Docker

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_onconditionで起動順序を保証(service_started/service_healthy/service_completed_successfully)
  • 複数ネットワーク設計(frontend/backend分離で攻撃面を減らす)
  • 環境変数/env_filesecretsの使い分け
  • Compose Watchでホスト変更をコンテナへ自動同期(2024年追加)
  • profilesで条件付きサービス(dev-tools/testing等)
  • compose.ymlcompose.override.ymlcompose.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)を一気に立ち上げる最小構成です。

compose.yml
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)

項目 V1(旧) V2(現行)
コマンド docker-compose(Python) docker compose(Go製プラグイン)
version:キー 必須("3.9"等) 不要(指定すると警告)
ファイル名 docker-compose.yml compose.yml(短縮形優先)
Watchモード なし docker compose watch追加(2024)
BuildKit オプション デフォルト(高速・並列ビルド)
Compose Spec Docker独自 OSS仕様化、他ツール互換
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

アプリ→DBへの接続例
# 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 でホスト側にアクセス

host.docker.internal等
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種

condition 意味 用途
service_started コンテナが起動した(デフォルト) healthcheckがないサービス/軽量依存
service_healthy healthcheckがhealthyを返した DBなど起動に時間がかかる依存(推奨)
service_completed_successfully プロセスが正常終了(exit 0) マイグレーション/シード投入等のワンショット
典型的なhealthcheck実装
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

マイグレーション完了を待ってから本体起動

service_completed_successfullyの例
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秒推奨)、②testCMD配列形式(shellエスケープ不要)かCMD-SHELL、③環境変数は$$でエスケープ($だとホスト展開される)、④retries× interval で最大待機時間を見積もり。

環境変数・env_file・secretsの使い分け

方式 用途 機密情報に使える?
environment:直書き 非機密な設定(TZ等) ✗ Git流出リスク
.envファイル(Compose標準) 開発者ごとの設定値 △ .gitignore必須
env_file:明示指定 サービス別に設定ファイル分離 △ .gitignore必須
secrets: APIキー・証明書・パスワード ◎ ファイルマウント/環境変数に載らない
.env(プロジェクト直下、Composeが自動読込)
COMPOSE_PROJECT_NAME=myapp
DB_HOST=db
DB_USER=app
DB_PASSWORD=localdev
APP_ENV=development
compose.yml(.envを参照)
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
secretsの実装
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によるパフォーマンス問題を解決します。

compose.yml(watchセクション)
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)/テスト用サービス/デバッグ支援などを必要時のみ立ち上げる定石。

profiles設定例
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.ymlcompose.prod.ymlに分けるのが実務標準。

compose.yml(共通)
services:
  app:
    image: app:latest
    networks: [app-net]

  db:
    image: mariadb:11
    networks: [app-net]

networks:
  app-net:
compose.override.yml(開発用、自動で重なる)
# デフォルトで自動マージされるファイル
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]
compose.prod.yml(本番用、明示指定)
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に書く。またはMakefileTaskfilemake prod等のタスク化するのが実戦的。

restart・resource limit・loggingで安定運用

restart policy

再起動ポリシー4種
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)

CPU/メモリ上限
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(安定化)

PID 1問題の回避と書込み制限
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}で明示チェック

よくある質問

Qdocker-compose(V1)はまだ使える?
AV1は2023年に公式サポート終了済み。Docker Desktopに含まれるV2(docker compose、スペース区切り)に移行してください。記法はほぼ互換ですが、version:キー撤廃/WatchやBuildKit標準化などV2独自の改善を享受できます。
Qサービス間でlocalhostが繋がらない
A各コンテナ内のlocalhostそのコンテナ自身を指します。他サービスに繋ぐにはサービス名をホスト名として使います(例:mysql://db:3306)。Composeの内部DNSが自動解決するため、127.0.0.1localhostをコード内で指定しないのがポイント。
Qdepends_onだけでは起動順序が保証されない?
Adepends_on: [db]だけでは「コンテナが起動した」までしか待ちません。DBの準備完了(接続可能状態)まで待つにはcondition: service_healthyとDB側のhealthcheckが必須。本記事「healthcheck + depends_on conditionで起動順序を保証」セクション参照。
Qcompose.ymlとdocker-compose.ymlどちらが正しい?
Aどちらも読まれますがcompose.yml優先で、公式は短縮形を推奨。新規プロジェクトはcompose.ymlcompose.override.ymlがCompose V2時代のスタンダード。
Qprofilesを設定したサービスをデフォルトでも動かしたい
A一部サービスのみ条件付きにするためのprofilesです。「デフォルト動作+特定時に追加」にしたい場合は、profiles指定なしのサービスとprofiles指定ありのサービスを混ぜてください。複数profilesを同時有効にしたい時は--profile a --profile bと並べるかCOMPOSE_PROFILES=a,b環境変数で指定。
Q本番でCompose使うのはアリ?
Aシングルノードなら十分アリ。ただし高可用性/オートスケーリング/ローリングアップデートが必要な本番はKubernetes(EKS/GKE/AKS)やDocker Swarmが適切。小〜中規模プロジェクトの本番ならComposeのみでも十分で、deploy.resourcesrestart: alwaysloggingsecretsを本番向けにチューニングすればOK。
QComposeからKubernetesへ移行する方法は?
Akompose convertcompose.ymlをKubernetes Manifest(Deployment/Service/PVC)に自動変換できます。生成後は手動調整が必要ですが出発点として有用brew install komposeでインストール→kompose convert -f compose.ymlで実行。
Q同じcompose.ymlを複数フォルダで起動するとコンテナ名がぶつかる
AComposeはフォルダ名をCOMPOSE_PROJECT_NAMEとして使います。別名にしたい時はdocker compose -p myapp2 up.envCOMPOSE_PROJECT_NAME=myapp2を書きます。各プロジェクトに独立したネットワーク/ボリュームが作られ、衝突を避けられます。
Q環境変数の展開を抑制したい($をそのまま使いたい)
A$$(ドル記号2つ)でエスケープします。例:PostgreSQLのhealthcheckでpg_isready -U $$POSTGRES_USERと書けば、ホスト側では展開されずコンテナ内のシェルで$POSTGRES_USERとして処理されます。
Qbuild済みイメージをチームで共有したい
AGHCR(GitHub Container Registry)へpushしてimage: ghcr.io/myorg/app:v1.2.3で参照するのが定番。GitHub Actionsで自動ビルド&pushするワークフローは【GitHub】Actions完全ガイドを参照。タグバージョン管理は【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 Watchdevelop.watch)でホスト変更を軽量に自動同期
  • profilesでdev-tools/testing/monitoringを条件付き起動
  • compose.ymloverride.ymlcompose.prod.ymlで環境別に分離
  • restart: unless-stoppeddeploy.resourcesloggingで安定運用
  • init: trueread_onlytmpfsでPID 1問題回避+書込み制限
  • デバッグ3種の神器:docker compose configlogs -fexec bash

Docker Composeは「複数コンテナを連携させる」表面機能だけでなく、healthcheck連鎖・複数ネットワーク分離・profiles条件起動・override環境分離・secrets/resource limitsまで含めたオーケストレーション基盤として本領を発揮します。本記事の完全版compose.ymlとベストプラクティスを土台にすれば、個人開発から中規模本番運用まで一気通貫で設計でき、いずれKubernetesへ移行する場合もkompose convertで出発点にできます。Web(NginxPHP-FPM)、DB(MySQL CLI)、CI/CD(GitHub Actions)と組み合わせて運用してください。