Docker Composeのdepends_onとHEALTHCHECK|サービス起動順序を確実に制御する設計パターン

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Docker > Docker Composeのdepends_onとHEALTHCHECK|サービス起動順序を確実に制御する設計パターン
「docker compose up を実行したのに、アプリコンテナがDB接続エラーで即座に落ちる」——この問題の原因は、DBコンテナのプロセスが起動しても「接続を受け付ける準備」が整う前にアプリが接続を試みることです。

Docker Compose の depends_on に condition: service_healthy を指定し、Dockerfile の HEALTHCHECK 命令を組み合わせると、DBが完全に起動するまでアプリコンテナの起動を遅らせることができます。

この記事では、depends_on の3種類のconditionとHEALTHCHECKの設定方法を、PostgreSQL・MySQL・Redisへの適用例を交えて解説します。curl・nc・専用CLIを使ったヘルスチェックパターンとexec形式・shell形式の使い分け、unhealthyになった場合のデバッグ手順(docker inspect・docker events活用)も含めて網羅しています。profiles機能を使った環境別サービスの選択起動と、profiles付きサービスへのdepends_on設計パターンも解説します。また、DBマイグレーション・テスト実行・シードデータ投入など「1回きり処理」を確実に実行する docker compose run の実践パターンと、起動中コンテナへの調査・デバッグに使う docker compose exec の使い分けも取り上げます。Compose v2.20以降で使える include ディレクティブを使った compose.yml のファイル分割パターンも解説します。RHEL 9.4 / Ubuntu 24.04 LTS + Docker 26.x / Compose v2.x(include機能はv2.20以降)を想定しています。

この記事のポイント

・depends_on の condition: service_healthy でDBの準備完了を待ってからアプリを起動できる
・HEALTHCHECK 命令でコンテナの「準備完了」基準を定義する(Dockerfile / Composeファイル両方に書ける)
・MySQLは -h 127.0.0.1・start_period: 30s が必須の設定ポイント
・docker inspect と docker events でヘルスチェックのログと進捗をリアルタイム確認できる
・profiles: [dev] でサービスを環境別に選択起動し、開発ツールを本番に混入させない(Compose v2.2以降)
・adminerのポートを 127.0.0.1:8081:8080 にバインドするとdevプロファイルが誤起動されても外部アクセスを防げる
・Mailpit等のSMTPモックを devプロファイルに隔離し、SMTP接続先を環境変数デフォルト値で切り替えられる
・--profile オプションは docker compose の直後・サブコマンドの前に指定する
・initプロファイルを使えばシードデータ投入などの一回限り処理を分離できる
・testプロファイルで専用DBコンテナを分離すればCI専用のクリーンなDB環境を用意できる
・COMPOSE_PROFILES 環境変数や .env ファイルで起動プロファイルを環境ごとに自動切り替えできる
・本番サーバーは /etc/environment や systemd の EnvironmentFile に COMPOSE_PROFILES= を書いて管理できる
・docker compose config --profiles で定義済みプロファイル一覧を確認できる(Compose v2.15以降)
・profiles付きサービスをdepends_onで参照するときは依存関係の設計に注意が必要
・docker compose run --rm でマイグレーション・テスト・シードを1回きり実行できる(終了後コンテナ自動削除)
・GitHub Actions から docker compose --profile ci run --rm migrate でCIマイグレーションを一時起動できる
・docker compose run と docker compose exec の使い分けは「サービスが停止中か起動中か」で判断する
・docker compose exec -it サービス名 bash で起動中コンテナにシェルセッションを開いてデバッグできる
・docker compose run --rm --entrypoint bash でENTRYPOINTを無視してbashを起動しコンテナ内を調査できる
・「no such service」はサービス名のタイポ確認、「container not running」は exec から run への切り替えで解決できる
・deploy.replicas でスケールした各レプリカには独立してヘルスチェックが実行される(Compose v2.x は Swarm なしでも有効)
・deploy.resources.limits でコンテナごとのCPU・メモリ上限を設定し、レプリカ増加時のリソース過消費を防ぐ
・debug プロファイルで netshoot コンテナを app コンテナのネットワーク名前空間に接続し、本番コンテナへの影響ゼロで ss / tcpdump / curl によるネットワーク診断ができる
・.env.dev / .env.production のように環境別 .env ファイルを用意し、cp .env.dev .env だけでプロファイルを自動切り替えできる
・--profile test up --abort-on-container-exit --exit-code-from で CI テスト全体の起動・実行・停止を1コマンドで制御できる
・include ディレクティブで compose.yml を複数ファイルに分割しながら1プロジェクトで管理できる(Compose v2.20以降)
・include で分割しても depends_on のサービス参照と service_healthy による起動順序制御はそのまま機能する
・docker compose config --services でインクルードを含む全サービス一覧を確認できる
・include はファイル全体を取り込む機能で、サービス定義を継承する extends とは目的が異なる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜ起動順序の制御が必要なのか

Docker Compose は depends_on を指定しなければ、すべてのコンテナをほぼ同時に起動します。DBが起動完了する前にアプリが接続を試みると、実際には次のようなエラーが発生します。

# PostgreSQLの場合 app_1 | sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) app_1 | could not connect to server: Connection refused app_1 | Is the server running on host "db" (172.18.0.2) and accepting app_1 | TCP/IP connections on port 5432? # MySQLの場合 web_1 | Error: connect ECONNREFUSED 172.20.0.2:3306 web_1 | sqlalchemy.exc.OperationalError: (pymysql.OperationalError) web_1 | (2003, "Can't connect to MySQL server on 'db' (111)")

depends_on を指定するだけでは「コンテナが起動した(プロセスが開始した)」ことを待つだけです。PostgreSQLがクライアントからの接続を受け付けるまでには、起動プロセスの完了・WAL再生・リスナー開始など数秒のラグがあります。MySQLでは、初期化スクリプト(/docker-entrypoint-initdb.d/)の実行・InnoDBバッファプールのウォームアップ・ポート3306の開放まで、さらに時間がかかります。データ量が多いDBや初回起動時のinitスクリプト実行を含む場合、このラグは10秒以上になることもあります。本番環境でも同様のトラブルが発生しやすい箇所です。

このラグを確実に吸収するのが ヘルスチェック(HEALTHCHECK) と condition: service_healthy の組み合わせです。「DBプロセスが起動した」ではなく「DBが接続リクエストに応答できる状態になった」ことを確認してからアプリを起動する仕組みです。

depends_onの基本と3種類のcondition

1. service_started(デフォルト・プロセス起動のみ確認)

condition を省略すると service_started が適用されます。コンテナのプロセスが起動した時点で依存関係を満たしたとみなします。DBが接続を受け付ける前に依存コンテナが起動する可能性があるため、DB待ちには使えません。

# condition を省略した例(service_started と同義) services: app: image: myapp:latest depends_on: - db # プロセス起動のみ確認。DB準備完了は保証しない db: image: postgres:16

2. service_healthy(ヘルスチェック通過を待つ)

最も実用的な設定です。依存先コンテナのヘルスチェックが healthy になるまで待機してから、依存元コンテナを起動します。本番・ステージングを問わず、DBが接続を受け付ける状態になってからアプリを起動したい場合はこれを使います。

services: app: image: myapp:latest depends_on: db: condition: service_healthy # dbがhealthyになるまで起動しない db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5

condition: service_healthy を使うには、依存先コンテナにヘルスチェックが設定されている必要があります。ヘルスチェックがないと service_healthy は永遠に待ち続けます(注意点は後述)。

3. service_completed_successfully(initコンテナの完了を待つ)

DBマイグレーションや初期データ投入など、一度だけ実行して終了するコンテナ(initコンテナ)の完了を待ちたい場合に使います。本番環境でスキーマ変更を伴うデプロイ時に頻繁に使われます。

services: app: image: myapp:latest depends_on: db: condition: service_healthy migrate: condition: service_completed_successfully # マイグレーション完了後に起動 migrate: image: myapp:latest command: python manage.py migrate depends_on: db: condition: service_healthy # マイグレーション自体もDBのhealthy待ち db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5

上記の例では、起動順序は db → migrate → app の順に確実に制御されます。CI/CDパイプラインでのデプロイ自動化にも、そのまま応用できる構成です。

Composeファイルに組み込まず、コマンドラインから即興で1回きりの処理を実行したい場合は、後述の docker compose run が有効です。用途に応じて使い分けてください。

HEALTHCHECK命令でコンテナの「準備完了」を定義する

HEALTHCHECKは「このコンテナが正常に機能している状態とは何か」を定義する仕組みです。ヘルスチェックコマンドが終了コード 0 を返せば「healthy(正常)」、1を返せば「unhealthy(異常)」と判定されます。設定方法は2種類あります。

1. Dockerfileでの設定

イメージに永続的に焼き込む場合は Dockerfile に記述します。独自イメージを作成する際はこちらを使います。

# PostgreSQLイメージに対するHEALTHCHECKの例 FROM postgres:16 # pg_isready でDBが接続を受け付けているか確認 HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=5 CMD pg_isready -U postgres || exit 1

オプションの意味は次のとおりです。

・--interval=5s:チェックを5秒ごとに実行する
・--timeout=5s:チェックコマンドが5秒以内に完了しない場合はfailとみなす
・--start-period=10s:起動後10秒はfailしてもretryカウントに含めない(起動猶予時間)
・--retries=5:5回連続でfailしたらunhealthyとみなす

2. docker-compose.ymlのhealthcheckセクションで設定する

Dockerfile を変更できない(公式イメージをそのまま使う)場合は、Composeファイル内で設定します。現場での採用頻度はこちらのほうが高いです。

services: db: image: postgres:16 environment: POSTGRES_USER: appuser POSTGRES_PASSWORD: secret POSTGRES_DB: appdb healthcheck: test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"] interval: 5s timeout: 5s start_period: 10s retries: 5

test に渡す文字列は CMD-SHELL 形式(シェルで実行)と CMD 形式(exec形式)があります。シェルのパイプや条件式を使う場合は CMD-SHELL を選びます。

MySQL と Redis のヘルスチェック例も示します。どちらも実務でよく組み合わせる構成です。

# MySQL 8.0 のヘルスチェック services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: appdb healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-psecret"] interval: 5s timeout: 5s start_period: 30s # MySQLは初期化に時間がかかるため長めに設定 retries: 5 # Redis のヘルスチェック(redis-cli ping が PONG を返せば healthy) services: redis: image: redis:7-alpine healthcheck: test: ["CMD-SHELL", "redis-cli ping | grep PONG"] interval: 5s timeout: 3s start_period: 5s retries: 3

MySQL の test では -h 127.0.0.1 を指定するのがポイントです。-h localhost にするとUnixソケット経由の接続になる場合があり、TCPポートが開いているかの確認にならないことがあります。また start_period: 30s は、MySQLの初期化スクリプト(/docker-entrypoint-initdb.d/)の実行時間を考慮した設定です。

Redis の test にパイプ(|)を使う場合は CMD-SHELL を指定します。redis-cli ping が正常なら PONG を返すため、grep PONG の成否でhealthy/unhealthyを判定できます。

3. exec形式とshell形式の使い分け

Dockerfile の HEALTHCHECK CMD は exec形式(JSON配列)と shell形式(文字列)の2通りで書けます。

# exec形式(JSON配列・シェルを経由しない・推奨) HEALTHCHECK --interval=10s --timeout=5s --retries=3 CMD ["curl", "-fsS", "http://localhost:8080/health"] # shell形式(/bin/sh -c を経由する・パイプを使いたい場合のみ) HEALTHCHECK --interval=10s --timeout=5s --retries=3 CMD redis-cli ping | grep PONG

exec形式はシェルを経由しないため、/bin/sh が存在しない distroless イメージや slim 系イメージでも動作します。パイプ(|)や条件演算子(&&、||)を使う必要がある場合のみ shell形式を選んでください。

終了コードのルールは次のとおりです。

・終了コード 0:healthy(正常)
・終了コード 1:unhealthy(異常)
・終了コード 2:Docker仕様で「予約済み」のため使用しない(将来の拡張用)

4. HEALTHCHECKを無効化する方法

ベースイメージがすでに HEALTHCHECK を設定している場合、子イメージでそれを無効化できます。デバッグ時や特定の環境でヘルスチェックを意図的に外したい場合に使います。

# 親イメージのHEALTHCHECKを無効化する FROM mybaseimage:latest HEALTHCHECK NONE

HEALTHCHECK NONE を指定すると、ベースイメージに設定されたヘルスチェックが子イメージで完全に無効化されます。docker ps の STATUS 列にはヘルスチェック情報が表示されなくなります。

実践例:PostgreSQL + Webアプリの起動順序を制御する

1. Composeファイルの全体構成

PostgreSQL と FastAPI(Python)アプリを組み合わせた構成例です。開発・本番環境でそのまま使える構成を示します。

# docker-compose.yml services: db: image: postgres:16 environment: POSTGRES_USER: appuser POSTGRES_PASSWORD: secret POSTGRES_DB: appdb healthcheck: test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"] interval: 5s timeout: 5s start_period: 10s retries: 5 volumes: - pgdata:/var/lib/postgresql/data app: build: . ports: - "8000:8000" environment: DATABASE_URL: "postgresql://appuser:secret@db:5432/appdb" depends_on: db: condition: service_healthy # dbがhealthyになってから起動 restart: on-failure # 接続失敗時にリスタート volumes: pgdata:

restart: on-failure は、ヘルスチェック通過後に万が一DB接続に失敗した場合の保険です。

2. ヘルスチェックの動作確認

docker compose up を実行すると、DBコンテナのヘルスチェックが通過するまで app コンテナの起動が待機されます。

$ docker compose up -d [+] Running 2/2 ✔ Container sample-db-1 Healthy 6.5s ✔ Container sample-app-1 Started 6.8s $ docker compose ps NAME IMAGE STATUS PORTS sample-app-1 myapp:latest Up 10 seconds 0.0.0.0:8000->8000/tcp sample-db-1 postgres:16 Up 15 seconds (healthy) 5432/tcp

「Healthy 6.5s」の数字はDBがhealthyになるまでの待機時間です。この間、app コンテナは起動を保留されています。docker compose ps の STATUS 列には次の3種類の状態が表示されます。

・(starting):--start-period の猶予期間中か、初回チェック待ち
・(healthy):直近のチェックが成功した状態
・(unhealthy):--retries 回連続してチェックが失敗した状態

(healthy) と表示されていれば、起動順序の制御が正常に機能しています。

3. MySQL + マイグレーションを含む多段depends_on構成

MySQLを使い、かつマイグレーション完了後にWebアプリを起動したい場合の構成です。db → migrate → web の順序を確実に保てます。

services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: myapp MYSQL_USER: appuser MYSQL_PASSWORD: apppass volumes: - db_data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-prootpass"] interval: 10s timeout: 5s retries: 5 start_period: 30s migrate: image: myapp:latest command: python manage.py migrate depends_on: db: condition: service_healthy environment: DATABASE_URL: mysql+pymysql://appuser:apppass@db:3306/myapp web: image: myapp:latest ports: - "8000:8000" depends_on: db: condition: service_healthy migrate: condition: service_completed_successfully # マイグレーション完了後に起動 environment: DATABASE_URL: mysql+pymysql://appuser:apppass@db:3306/myapp volumes: db_data:

dbが healthyになってから migrate が起動し、migrate が 正常終了してから web が起動します。マイグレーション未適用のDBにアプリがアクセスするリスクを排除できます。 Docker Composeの設計パターンや本番運用の学習を体系的に進めたい方は、Dockerマスター講座も参照してください。

ヘルスチェックが unhealthy になった場合のデバッグ手順

1. docker inspect でステータスとログを確認する

docker inspect を使うと、ヘルスチェックの詳細な実行履歴が取得できます。障害発生時の第一調査コマンドとして覚えておいてください。

# コンテナ名を確認 $ docker compose ps # ヘルスチェック詳細を取得(JSON整形表示) $ docker inspect --format='{{json .State.Health}}' sample-db-1 | python3 -m json.tool # 出力例(失敗している場合) { "Status": "unhealthy", "FailingStreak": 3, "Log": [ { "Start": "2026-07-07T08:00:01.234Z", "End": "2026-07-07T08:00:01.239Z", "ExitCode": 1, "Output": "pg_isready: error: connection to server at localhost failed" } ] }

Output フィールドにヘルスチェックコマンドの標準エラーが出力されます。このメッセージを起点に原因を特定します。

ExitCode の値は次のように解釈します。

・ExitCode: 0:healthy(チェック成功)
・ExitCode: 1:unhealthy(チェックコマンドがエラーを返した)
・ExitCode: 124:タイムアウト(--timeout 内にコマンドが応答しなかった)

ExitCode が 124 の場合は --timeout の値を延ばすか、ヘルスチェックコマンド自体の応答速度を確認してください。

2. docker events でヘルスチェックの進捗をリアルタイム確認する

docker compose up を実行しながら別ターミナルで docker events を流すと、コンテナの health_status 変化をリアルタイムで把握できます。「いつhealthyになったか」「unhealthyが何回続いているか」の確認に便利です。

# health_statusイベントのみフィルタ(別ターミナルで実行) $ docker events --filter event=health_status 2026-07-07T08:00:01.000000000+09:00 container health_status sample-db-1 (health_status=starting) 2026-07-07T08:00:11.000000000+09:00 container health_status sample-db-1 (health_status=unhealthy) 2026-07-07T08:00:51.000000000+09:00 container health_status sample-db-1 (health_status=healthy)

start_period 中は starting と表示され、unhealthy が続いた後に healthy へ遷移するまでの流れが一目でわかります。

3. docker compose config でhealthcheck設定を確認する

docker inspect の前に docker compose config で設定が正しく解釈されているか確認する方法も有効です。YAMLのインデントずれや誤字で healthcheck が意図どおりに定義されていない場合、この手順で早期に気づけます。

# Composeファイルの解釈結果を表示する(パースエラーも検出できる) $ docker compose config # 特定サービスのhealthcheck設定だけ確認する $ docker compose config | grep -A 8 'healthcheck' healthcheck: interval: 5s retries: 5 start_period: 10s test: - CMD-SHELL - pg_isready -U appuser -d appdb timeout: 5s

4. testコマンドをコンテナ内で手動実行して確認する

healthcheckのtestコマンドが本当に動くかは、コンテナ内で直接実行して終了コードを確認するのが一番確実です。

# MySQLコンテナに入ってmysqladmin pingを直接実行 $ docker compose exec db mysqladmin ping -h 127.0.0.1 -u root -prootpass mysqladmin: [Warning] Using a password on the command line interface can be insecure. mysqld is alive # exit codeが0かどうか確認(0ならhealthyと判定される) $ echo $? 0

echo $? が 0 を返せば、同じtestコマンドが healthcheck として動いたときも healthy と判定されます。1 が返る場合はコマンドの引数(ユーザー名・パスワード・ホスト名)を見直してください。

5. よくある原因と対処

・pg_isreadyのユーザー名が違う:-U オプションで指定するユーザー名が POSTGRES_USER と一致しているか確認
・MySQLで -h localhost を使っている:Unixソケット経由になる場合がある。-h 127.0.0.1 に変更する
・start_period が短すぎる:MySQLは start_period: 30s、PostgreSQLは start_period: 10s を目安に、init処理の実際の所要時間に合わせて調整する
・ネットワーク名前解決の失敗:ヘルスチェックはコンテナ自身の内部で実行されるため、localhost または 127.0.0.1 を使う(サービス名は使えない)
・testコマンドがコンテナ内に存在しない:Alpine や slim 系イメージには curl が入っていないことがある。その場合は nc や専用CLI(pg_isready・mysqladmin ping・redis-cli ping)で代替する

curlを使ったWebサーバーのヘルスチェック例と、curlが使えない場合のncによる代替例を示します。

# curlによるHTTPエンドポイントの確認(-f: 4xx/5xxで終了コード22を返す) healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 10s timeout: 5s start_period: 15s retries: 3 # curlがない場合はncでTCPポート確認(-z: 接続のみ -w 1: 1秒タイムアウト) healthcheck: test: ["CMD", "nc", "-z", "-w", "1", "localhost", "8080"] interval: 10s timeout: 5s start_period: 15s retries: 3

curl の -f オプションは HTTP 4xx/5xx レスポンスを受け取ったときに終了コード 22 を返すため、アプリがエラーレスポンスを返している状態も unhealthy として検出できます。

6. ヘルスチェックが永遠に待ち続ける場合

condition: service_healthy を設定したのに依存先コンテナに healthcheck: が定義されていない場合、Docker Composeはタイムアウトなしで無限に待ち続けます。docker compose up が一向に完了しない場合はこれが原因の場合があります。

# ヘルスチェックが設定されているか確認する $ docker inspect sample-db-1 --format='{{json .Config.Healthcheck}}' # 設定なしの場合(healthcheckを追加する必要がある) null # 設定ありの場合 {"Test":["CMD-SHELL","pg_isready -U appuser -d appdb"],"Interval":5000000000,"Timeout":5000000000,"StartPeriod":10000000000,"Retries":5}

Interval などの数値はナノ秒単位です(例:5000000000ns = 5秒)。null が返った場合は healthcheck セクションを追加してください。

profilesで環境別にサービスを選択起動する

Composeで構成するサービスが増えてくると、次のような「全部入り」の compose.yaml が生まれがちです。

# 問題のある「全部入り」compose.yaml の例 services: app: image: myapp:latest ports: - "8080:8080" db: image: postgres:16 volumes: - db-data:/var/lib/postgresql/data # 開発時だけ使いたいDB管理ツール pgadmin: image: dpage/pgadmin4:latest ports: - "5050:80" # 障害調査時だけ使いたい監視ツール prometheus: image: prom/prometheus:latest ports: - "9090:9090" grafana: image: grafana/grafana:latest ports: - "3000:3000" volumes: db-data:

この構成で docker compose up -d を実行すると、本番サーバーでも pgAdmin と Prometheus / Grafana が起動します。開発時しか使わないツールが本番で動き続け、メモリと CPU を無駄に消費します。ポート番号の衝突リスクも高まります。

「docker compose up を実行したら、開発環境でしか使わないデバッグツールが本番サーバーで起動してしまった」——こうした事故を防ぐのが profiles 機能です。Docker Compose v2.2(2022年1月リリース)以降で使えます。サービスに1つ以上のプロファイル名を付け、docker compose --profile dev up のように指定することで、そのプロファイルに属するサービスだけを選択起動できます。

なお、profiles は Compose Spec(Docker Compose v2系) で追加された機能です。古い docker-compose(v1系・Python製)では動作しません。現在の推奨は docker compose(v2系・Go製プラグイン)です。CIや古い検証機でv1系が残っている場合は docker compose version でバージョンを確認してください。

compose.override.yml を使った環境ごとのファイル分割と比べると、1つのcompose.ymlで全環境の構成が完結するため差分が追いやすく、CIパイプラインに渡すファイルを切り替える必要もありません。

--profile オプションは docker compose の直後(サブコマンドの前)に指定します。サブコマンドの後ろに書いても無視されるため、位置に注意してください。

profilesを使ったサービス定義の基本

# compose.yml services: # profilesなし → 全環境で常に起動する共通サービス app: build: . depends_on: db: condition: service_healthy db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s retries: 5 # devプロファイル専用(--profile dev 指定時のみ起動) adminer: image: adminer profiles: [dev] ports: - "8080:8080" # devとstagingの両環境で使うメールトラップ(Mailpit) mailpit: image: axllent/mailpit profiles: [dev, staging] ports: - "1025:1025" # SMTP - "8025:8025" # Web UI # 本番・負荷試験環境の監視スタック prometheus: image: prom/prometheus:latest profiles: [monitoring] volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090"

profiles: キーを持たないサービス(app・db)は、プロファイル指定の有無にかかわらず常に起動します。全環境で必須のサービスにはprofilesを付けないのが正しい設計です。

各環境での起動コマンドと起動対象サービスは次のとおりです。

環境 起動コマンド 起動するサービス
本番 docker compose up -d app、db
開発 docker compose --profile dev up -d app、db、adminer、mailpit
ステージング docker compose --profile staging up -d app、db、mailpit
本番+監視 docker compose --profile monitoring up -d app、db、prometheus
profilesを指定した場合と指定しない場合で実際に何が起動するかを確認しておきましょう。

# profilesなしで起動(デフォルトサービスのみ) $ docker compose up -d [+] Running 2/2 ✔ Container myapp-db-1 Started ✔ Container myapp-app-1 Started $ docker compose ps NAME IMAGE STATUS PORTS myapp-app-1 myapp:latest Up 0.0.0.0:8000->8000/tcp myapp-db-1 postgres:16 Up 5432/tcp # devプロファイルを指定(開発ツールが追加起動される) $ docker compose --profile dev up -d [+] Running 4/4 ✔ Container myapp-db-1 Running ✔ Container myapp-app-1 Running ✔ Container myapp-adminer-1 Started ✔ Container myapp-mailpit-1 Started

既存のサービスは再起動せず、プロファイルに対応するサービスだけが追加起動されます。この設計により、1つのcompose.ymlで全環境の構成管理が完結します。CI/CDパイプラインでは環境変数 COMPOSE_PROFILES を切り替えるだけで対応できます。

パターン1: 監視スタックをmonitoringプロファイルに分離する

PrometheusとGrafanaはスクレイピング間隔ごとにDBへ書き込みを行うリソース消費の大きいサービスです。本番環境では必要ですが、開発中は不要なことがほとんどです。

services: app: image: myapp:latest ports: - "8080:8080" prometheus: image: prom/prometheus:v2.52.0 profiles: - monitoring ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus grafana: image: grafana/grafana:10.4.0 profiles: - monitoring ports: - "3000:3000" environment: GF_SECURITY_ADMIN_PASSWORD: admin volumes: - grafana_data:/var/lib/grafana depends_on: - prometheus volumes: prometheus_data: grafana_data:

monitoringプロファイルを持つサービスはPrometheusとGrafanaのみです。本番環境でのデプロイ時に COMPOSE_PROFILES=monitoring を追加するだけで監視スタックが有効になります。

パターン2: シードデータ投入コンテナをinitプロファイルに分離する

データベースに初期データを投入する一回限りの処理は、通常のサービスとして常駐させる必要はありません。initプロファイルに分離し、必要なときだけ起動して終了させます。restart: "no" を設定すれば、シード処理が完了したら自動的に停止します。

services: db: image: postgres:16 environment: POSTGRES_PASSWORD: secret POSTGRES_DB: mydb healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 3s retries: 10 db-seed: image: myapp:latest profiles: - init command: ["python", "manage.py", "seed_data"] environment: DATABASE_URL: postgresql://postgres:secret@db:5432/mydb depends_on: db: condition: service_healthy restart: "no"

# DBが healthy になるのを待ってシードを実行する docker compose --profile init up db-seed # 終了したコンテナをクリーンアップ docker compose --profile init rm db-seed

本番データベースの初期セットアップやマイグレーション実行に応用できます。service_completed_successfully と組み合わせることで「シード完了後にアプリを起動」という多段依存も構成できます。なお、initプロファイルを使わずにその場でワンショット実行したい場面では、後述の docker compose run --rm が手軽な選択肢になります。

パターン3: 1つのサービスに複数のprofilesを割り当てる

開発環境・ステージング環境の両方で使いたいサービスには、複数のプロファイル名をリストで指定します。どちらのプロファイルを指定して起動しても対象サービスが含まれます。

services: redis-commander: image: rediscommander/redis-commander:latest profiles: - dev - staging ports: - "8081:8081" environment: REDIS_HOSTS: local:redis:6379 depends_on: - redis

この設定では、--profile dev または --profile staging のどちらを指定しても redis-commander が起動します。本番(productionプロファイル)では起動しません。

複数のプロファイルをまたぐ別の例として、開発環境とデバッグ環境の両方で使うRedis管理UIを dev と debug の2つのプロファイルに対応させる構成も実務で役立ちます。

services: redis: image: redis:7-alpine redis-insight: image: redislabs/redisinsight:latest ports: - "127.0.0.1:5540:5540" profiles: - dev - debug

dev または debug のいずれかを指定するとRedisInsightが起動します。ポートを 127.0.0.1:5540:5540 にバインドしているため、万が一意図せず起動された場合も外部からアクセスできません。

パターン4: profilesサービス間の依存(同じprofiles名を共有する)

profilesが付いたサービス同士で depends_on を使う場合、依存関係にある両方のサービスに同じプロファイル名を付ける必要があります。profiles名が食い違うと起動時にエラーが発生します。

# 正しい設計:依存するサービス間で同じprofiles名を共有する services: grafana: image: grafana/grafana depends_on: - prometheus profiles: - monitoring # prometheusと同じプロファイル名 prometheus: image: prom/prometheus profiles: - monitoring # grafanaと同じプロファイル名 # NG: profiles名が食い違う設計 services: grafana: image: grafana/grafana depends_on: - prometheus profiles: - monitoring # grafanaはmonitoringプロファイル prometheus: image: prom/prometheus profiles: - infra # prometheusはinfraプロファイル(食い違いでエラーになる)

--profile monitoring で起動しようとしても、prometheus が monitoring プロファイルを持っていなければ grafana の depends_on が解決できずエラーになります。デフォルトサービスからprofilesサービスへの依存と同様に、この組み合わせも設計段階で確認しておきましょう。

パターン5: DEV専用ツールを1プロファイルにまとめる

ローカル開発でよく使うツールをすべて profiles: [dev] にまとめておくと、docker compose --profile dev up の1コマンドで開発環境全体が整います。メールデバッグ用のMailpit、DB管理UIのadminer、ローカルS3互換のMinIOなど、本番には不要なツール群をまとめて定義するパターンです。

services: app: image: myapp:latest environment: SMTP_HOST: ${SMTP_HOST:-mailpit} # 環境変数未設定時はmailpitに接続 SMTP_PORT: ${SMTP_PORT:-1025} db: image: postgres:16 environment: POSTGRES_PASSWORD: secret # DEV専用: メールデバッグ(受信したメールをWeb UIで確認できるSMTPモック) mailpit: image: axllent/mailpit:latest profiles: [dev] ports: - "127.0.0.1:8025:8025" # Web UI(localhost限定バインド) - "127.0.0.1:1025:1025" # SMTP(localhost限定バインド) # DEV専用: DB管理UI adminer: image: adminer:latest profiles: [dev] ports: - "127.0.0.1:8080:8080" # localhost限定バインド(誤起動時の外部公開を防ぐ) depends_on: - db # DEV専用: ローカルS3互換(S3 APIのローカル検証に使う) minio: image: minio/minio:latest profiles: [dev] command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: devuser MINIO_ROOT_PASSWORD: devpassword ports: - "9000:9000" - "9001:9001"

ポートのバインドに 127.0.0.1:8080:8080 と明示的に指定するのが実務上の重要なポイントです。0.0.0.0:8080:8080(デフォルト)にすると全インターフェースにバインドされ、外部から adminer のDB管理画面にアクセスできてしまいます。127.0.0.1 を指定しておくと、devプロファイルが本番環境で誤起動された場合でも外部からのアクセスを二重に遮断できます。

SMTP接続先を ${SMTP_HOST:-mailpit} と環境変数のデフォルト値で指定しておくと、開発環境では mailpit(SMTPモック)に、本番では実際のSMTPサーバーに自動的に切り替わります。本番の .env に SMTP_HOST=smtp.example.com を書くだけで切り替わるため、アプリのコード変更が不要です。

本番の .env に COMPOSE_PROFILES=dev を書かなければ、これらのサービスは一切起動しません。開発環境のセットアップを新しいメンバーへ共有する際も、「docker compose --profile dev up -d を実行するだけ」と伝えられるのでオンボーディングコストが下がります。

パターン6: debugプロファイルでネットワーク診断コンテナを本番から隔離する

障害調査時に、起動中の app コンテナのネットワーク状態を tcpdump や ss で確認したい場面があります。しかし、app コンテナ自体に診断ツールをインストールすると、本番イメージのサイズが増え、攻撃面も広がります。

nicolaka/netshoot(ネットワーク診断ツールを多数含むコンテナ)を debug プロファイルに分離し、network_mode: "service:app" で app コンテナのネットワーク名前空間を共有する構成を使うと、本番コンテナには一切手を加えずにネットワーク診断ができます。

services: app: image: myapp:latest ports: - "8080:8080" db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s retries: 5 # debug プロファイル専用のネットワーク診断コンテナ debug: image: nicolaka/netshoot profiles: - debug network_mode: "service:app" # app コンテナのネットワーク名前空間を共有 stdin_open: true tty: true command: ["sleep", "infinity"]

docker compose --profile debug up -d で debug コンテナを起動した後、次のように接続して診断できます。

# debug コンテナで診断(app コンテナのネットワーク名前空間を共有) $ docker compose --profile debug up -d $ docker compose exec debug bash # app コンテナの localhost:8080 に直接アクセス可能 bash-5.2# curl -s http://localhost:8080/ | head -3 # app コンテナが listening しているポートを確認 bash-5.2# ss -tlnp State Recv-Q Send-Q Local Address:Port LISTEN 0 511 0.0.0.0:8080 # TCP接続をリアルタイム監視 bash-5.2# tcpdump -i any -n tcp port 8080 # 調査終了後は debug コンテナのみ停止(app・db はそのまま) $ docker compose stop debug

network_mode: "service:app" を指定すると、debug コンテナは app コンテナと同じネットワーク名前空間を使います。debug コンテナからは localhost:8080 で app のポートに直接アクセスでき、ss や tcpdump などの診断ツールを app コンテナに影響を与えずに実行できます。Kubernetes の ephemeral container に近い設計パターンを Compose 環境で実現できます。

パターン7: テスト実行コンテナをtestプロファイルに分離する

CIパイプラインでテストを実行するとき、DBのマイグレーション・シード・テスト実行をまとめて1コマンドで制御したい場面があります。test プロファイルにマイグレーション用コンテナとテスト実行コンテナをまとめて定義しておくと、CI設定が大幅にシンプルになります。

テスト専用の独立したDBを用意したい場合は、本番DBと別のサービスとして db-test を定義します。本番DBに影響を与えずにテストを実行でき、テスト後は docker compose down -v でクリーンアップできます。

services: db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 # testプロファイル: テスト専用DBコンテナ(本番DBとは独立して起動) db-test: image: postgres:16 environment: POSTGRES_DB: testdb POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass profiles: - test healthcheck: test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"] interval: 5s timeout: 5s retries: 5 # testプロファイル: マイグレーション+シードをまとめて実行 migrate: image: myapp:latest profiles: - test command: ["sh", "-c", "python manage.py migrate && python manage.py seed"] depends_on: db-test: condition: service_healthy # testプロファイル: テスト実行コンテナ(マイグレーション完了後に起動) app-test: image: myapp:latest profiles: - test command: ["python", "-m", "pytest", "tests/"] environment: DATABASE_URL: postgresql://testuser:testpass@db-test:5432/testdb depends_on: migrate: condition: service_completed_successfully

CIコマンドは次のようになります。

# CIでテスト全体を一括実行(GitHub Actions の run: に書く) $ docker compose --profile test up --abort-on-container-exit --exit-code-from app-test # 実行ログ例(db-test → migrate → app-test の順に起動) [+] Running 4/4 ✔ Container myapp-db-1 Healthy ✔ Container myapp-db-test-1 Healthy ✔ Container myapp-migrate-1 Exited (0) ✔ Container myapp-app-test-1 Exited (0)

--abort-on-container-exit はいずれかのコンテナが終了した時点でスタック全体を停止します。--exit-code-from app-test はテストコンテナの終了コードをそのまま返すため、テストが失敗すれば非0が返り、CIジョブが落ちます。マイグレーション失敗・シードエラー・テスト失敗のいずれも確実に検知できます。

migrate・db-test・app-test に同じ test プロファイルを付けているため、本番の docker compose up ではこれらのコンテナは一切起動しません。テスト専用サービスが本番環境に混入するリスクをゼロにしたうえで、CI設定をシンプルに保てる設計です。

COMPOSE_PROFILES 環境変数と .env ファイルによる自動切り替え

毎回 --profile dev オプションを付けて起動するのが手間な場合は、環境変数 COMPOSE_PROFILES を使います。カンマ区切りで複数のプロファイルを同時に指定できます。

# 環境変数で直接指定(カンマ区切りで複数プロファイルを同時指定) export COMPOSE_PROFILES=dev,tools docker compose up -d # .env ファイルに書いておく方法(プロジェクトルートの .env に追記) COMPOSE_PROFILES=dev # .env が反映されているか確認 docker compose config | grep -A 1 COMPOSE_PROFILES

.env に COMPOSE_PROFILES=dev と書いておくと、docker compose up -d だけで自動的に dev プロファイルが有効になります。本番サーバーの .env にはこの行を書かなければ開発専用サービスは起動しません。.env を .gitignore で管理対象外にし、環境ごとの雛形として .env.example だけをリポジトリに含める運用と相性の良い設計です。

本番サーバーでシェル変数レベルで固定管理したい場合は、/etc/environment に COMPOSE_PROFILES=(空値)と書くか、systemd サービスの EnvironmentFile に専用ファイルを指定する方法も実務で使われます。デプロイスクリプト側でプロファイルを意識せず docker compose up -d を実行できます。

# 本番サーバーの /etc/environment に書く例(プロファイルなし=devツール不起動) COMPOSE_PROFILES= # systemd サービスの EnvironmentFile に指定する例 # /etc/myapp/compose-env COMPOSE_PROFILES=monitoring # systemd ユニットファイル(抜粋) [Service] EnvironmentFile=/etc/myapp/compose-env ExecStart=/usr/bin/docker compose up -d

compose.yml に定義されているプロファイル名の一覧を確認したい場合は docker compose config --profiles(Compose v2.15以降)が便利です。profilesなしで起動されるサービスの一覧確認には docker compose config --services が使えます。新しいサービスを追加した際の profiles: 付け忘れチェックにも有効です。

# 定義済みプロファイル一覧を確認(Compose v2.15以降) $ docker compose config --profiles dev monitoring staging # profilesオプションなしで起動するサービス一覧を確認 $ docker compose config --services app db # profiles を付けたサービスはここに出てこない # 特定のプロファイルを指定した場合のサービス一覧を確認 $ docker compose --profile dev config --services app db adminer mailpit

環境別に .env ファイルを用意して cp で切り替える方法も実務で広く使われています。環境ごとに異なるパスワードとプロファイルを .env.環境名 として管理し、cp .env.dev .env だけで全設定を切り替えられます。

# .env.dev(開発環境用) DB_PASSWORD=devpassword COMPOSE_PROFILES=dev # .env.monitoring(監視有効化用) DB_PASSWORD=prodpassword COMPOSE_PROFILES=monitoring # .env.production(本番用。プロファイルは何も指定しない) DB_PASSWORD=strongpassword COMPOSE_PROFILES= # 開発環境を起動する $ cp .env.dev .env $ docker compose up -d # → COMPOSE_PROFILES=dev が読み込まれ、devプロファイルのサービスも起動する # 本番環境を起動する $ cp .env.production .env $ docker compose up -d # → COMPOSE_PROFILES= なので devサービスは起動しない

.env を .gitignore で管理対象外にし、環境ごとの雛形として .env.example だけをリポジトリに含める運用と相性が良い設計です。複数プロファイルを有効にしたい場合は COMPOSE_PROFILES=dev,monitoring のようにカンマ区切りで指定します。

特定プロファイルのサービスだけを停止したい場合は、プロファイル名でまとめて停止する docker compose --profile dev stop か、サービス名を直接指定する docker compose stop を使います。docker compose --profile dev down はすべてのサービスを削除するため注意が必要です。

# dev プロファイルのサービスをプロファイル名でまとめて停止する(推奨) $ docker compose --profile dev stop [+] Stopping 2/2 ✔ Container myapp-adminer-1 Stopped ✔ Container myapp-mailpit-1 Stopped # サービス名を直接指定して個別に停止する $ docker compose stop adminer # dev プロファイルのサービスを名前指定でまとめて停止する $ docker compose stop adminer mailpit

compose.ymlをincludeでファイル分割する(Compose v2.20以降)

profilesでサービスを環境別に切り替えながら管理できるようになると、次のフェーズとして「compose.yml そのものが長くなりすぎる」問題が出てきます。DB・アプリ・監視スタックを1ファイルに書き続けると、チームで分業するときのマージ競合も増えます。

Docker Compose v2.20 以降では include ディレクティブを使って compose.yml を複数ファイルに分割しながら、1つのプロジェクトとして管理できます。インクルード先のサービスは depends_on からそのまま参照できるため、本記事で解説した起動順序制御もそのまま機能します。

1. includeとextendsの違い

Docker Composeには、ファイルや設定の重複を減らす仕組みが複数あります。include と extends は混同されやすいですが、対象の単位と目的が異なります。

機能 対象単位 ユースケース
extends サービス定義(1サービス単位) 共通設定を継承してサービスを派生させる
include compose.yamlファイル全体 複数チームのcompose.yamlを1つに集約する
override ファイル compose.yamlファイル(差分上書き) ローカル環境の差分だけを別ファイルで管理する
extends は「あるサービスが別のサービス定義を継承して設定を上書きする」機能です。同一ファイル内または別ファイルのサービス設定を再利用したい場合に使います。サービス単位の再利用に強い機能です。

include は「compose.yamlファイル全体を取り込む」機能です。フロントエンドチーム・バックエンドチーム・インフラチームがそれぞれ独立したcompose.yamlを持ち、ルートのcompose.yamlがそれらをまとめて取り込む構成を実現できます。チームで分業するときのマージ競合を減らす効果があります。

override ファイル(compose.override.yml)は「既存のcompose.ymlに差分を上書きする」機能です。本番用の compose.yml に対して開発環境だけ変えたい設定を重ねる用途に使います。

2. includeの基本構文

include はトップレベルのキーとして compose.yml に記述します。

# compose.yaml(メインファイル) include: - infra/compose.yaml - monitoring/compose.yaml services: api: build: ./api depends_on: postgres: condition: service_healthy # infra/compose.yaml に定義した postgres を参照

infra/compose.yaml に postgres・redis を定義し、monitoring/compose.yaml に prometheus・grafana を定義すると、メインファイルの api サービスから depends_on でそれらを参照できます。ファイルは自動的にマージされ、docker compose up は全サービスを1プロジェクトとして起動します。

3. インフラ・アプリ・監視の3層分割パターン

実務でよく使う3層分割の構成例です。メインの compose.yaml は include の宣言だけにし、サービス定義は各サブディレクトリに分散させます。

myproject/ ├── compose.yaml # メインファイル(includeのみ記述) ├── infra/ │ ├── compose.yaml # postgres, redis │ └── .env ├── app/ │ ├── compose.yaml # api, worker │ └── Dockerfile └── monitoring/ ├── compose.yaml # prometheus, grafana(profilesと組み合わせも可) └── prometheus.yml

# compose.yaml(メイン) include: - path: infra/compose.yaml project_directory: ./infra - path: app/compose.yaml project_directory: ./app - path: monitoring/compose.yaml project_directory: ./monitoring

project_directory で各サブディレクトリを相対パスの基準として指定しておくと、インクルード先ファイル内のボリューム・ビルドコンテキストパスが正しく解決されます。省略した場合はインクルードされるファイルが置かれたディレクトリが自動的に基準になります。

docker compose up -d 後は docker compose config --services で全サービスが1プロジェクトに統合されているか確認します。

# インクルード後の全サービスを確認 $ docker compose config --services api grafana postgres prometheus redis # 分割ファイルを含めた最終的なYAML定義を確認(パースエラーも検出できる) $ docker compose config

4. includeとprofilesの組み合わせ

include と profiles は組み合わせて使えます。monitoring/ の compose.yaml に profiles: [monitoring] を付けたサービスを定義しておけば、ファイルを分割しながらプロファイルによる条件起動も維持できます。本番環境では profiles なしで起動し、--profile monitoring 指定時だけ監視スタックが追加起動するという設計が1ファイルのときと同様に機能します。

5. includeを使う際の注意点

・サービス名は全インクルードファイルで一意にする:異なる compose.yaml で同じサービス名を定義すると後から読み込まれた定義が前の定義を上書きします
・Compose v2.20以降が必要:古いバージョンでは無効なキーとして無視またはエラーになります。docker compose version で確認してください。Ubuntu 22.04の標準パッケージが古い場合はDockerの公式リポジトリから最新版をインストールし直す必要があります
・overrideファイル・extendsとの違いに注意する:include は「ファイル全体を取り込む」(ファイル粒度の分割)、extends はサービス設定の継承(サービス粒度の再利用)、override ファイルはローカル差分の上書きと、それぞれ目的が異なります

docker compose runによる1回きり実行パターン

docker compose up ではなく docker compose run を使うと、compose.yml の設定(イメージ・ボリューム・ネットワーク・環境変数)をそのまま流用しながら、指定したコマンドだけを1回限りのコンテナで実行できます。DBマイグレーション・シードデータ投入・テスト実行など「一度実行したら終了する処理」に最適なアプローチです。

service_completed_successfully との違いは、docker compose run がCompose定義外の即興的な実行に向いているのに対し、service_completed_successfully はCompose定義の中に組み込んで「サービスの依存関係として宣言する」ときに使う点です。

1. 基本の使い方と --rm オプション

基本構文は次のとおりです。サービス定義を流用して任意のコマンドを1回だけ実行します。

# 基本構文 docker compose run [オプション] サービス名 [コマンド [引数...]] # Djangoのマイグレーション(--rmで終了後コンテナを自動削除) $ docker compose run --rm app python manage.py migrate # Laravelのマイグレーション $ docker compose run --rm app php artisan migrate # Railsのマイグレーション $ docker compose run --rm app bin/rails db:migrate # pytestでテストを実行(終了コードがCIの成否判定に使える) $ docker compose run --rm app pytest tests/ -v

--rm を付けると実行終了後にコンテナが自動削除されます。付けない場合は停止済みコンテナが蓄積するため、ワンショット実行では必ず付けるのが運用の鉄則です。

docker compose run はデフォルトで depends_on に指定した依存サービスも自動起動します。ただし依存サービスのポートはホストに公開されません(コンテナ間では通信可)。依存サービスを起動したくない場合は --no-deps を付けてスキップします。

# 依存サービスを起動せずに staticfiles だけ収集する $ docker compose run --rm --no-deps app python manage.py collectstatic --noinput # DATABASE_URLだけをステージング環境のものに差し替えてマイグレーション $ docker compose run --rm -e DATABASE_URL=postgresql://user:pass@staging-db.example.com:5432/stg app python manage.py migrate

CIパイプラインでマイグレーションを実行する場合、profilesと組み合わせるパターンも実用的です。Webアプリコンテナを起動せずにマイグレーション専用コンテナだけを一時起動できます。GitHub Actions での例を示します。

# compose.yml の migrate サービスを ci プロファイルに分離する services: db: image: postgres:16 environment: POSTGRES_PASSWORD: ${DB_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s retries: 5 migrate: image: myapp:latest command: ["python", "manage.py", "migrate"] depends_on: db: condition: service_healthy profiles: - ci # CIプロファイル指定時のみ起動 app: image: myapp:latest depends_on: db: condition: service_healthy

# GitHub Actions の steps 例 - name: Run DB migration run: docker compose --profile ci run --rm migrate # CI実行ログ(DBが healthy になってからマイグレーションが走る) [+] Running 2/2 ✔ Network myapp_default Created ✔ Container myapp-db-1 Healthy Operations to perform: Apply all migrations: auth, contenttypes, myapp Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying myapp.0001_create_users... OK

migrate サービスに profiles: [ci] を付けておくことで、本番デプロイ時の docker compose up ではマイグレーションコンテナが起動しません。CIでは --profile ci を明示するだけで制御できます。
--entrypointでENTRYPOINTを上書きする

DockerfileにENTRYPOINTが設定されているコンテナで、それを一時的に無視してbashを起動したいときは --entrypoint オプションを使います。コンテナが起動しない原因の調査や、設定ファイルの中身の確認に活用できます。

# ENTRYPOINTを無視してbashを起動する docker compose run --rm --entrypoint bash app

2. docker compose execの基本的な使い方

docker compose exec は、docker compose up 等ですでに起動しているコンテナの中にコマンドを送り込むコマンドです。マイグレーションやバッチ処理のような「使い捨て実行」ではなく、稼働中コンテナのデバッグ・調査・設定確認に使います。

基本構文は次のとおりです。

docker compose exec [オプション] サービス名 コマンド

インタラクティブなシェルセッションを開く場合は -it フラグを付けます。

# 起動中の db コンテナにシェルで接続する docker compose exec -it db bash # Webサーバーの設定ファイルを確認する docker compose exec web cat /etc/nginx/nginx.conf # MariaDB コンテナで SQL を直接実行する docker compose exec -it db mariadb -u root -ppassword myapp # 起動中コンテナの環境変数を確認する docker compose exec app env | grep -i database

execを使う最大のメリットは、本番稼働中のプロセス・ファイル・環境変数をそのまま確認できる点です。新しいコンテナを作らないため、既存の接続・ロック・ファイルの状態が変わりません。ログだけでは原因が追えない問題が起きたとき、execでコンテナ内に入って直接調査できます。

3. docker compose runとexecの使い分け

runとexecの使い分けポイントを比較表で整理します。

項目 docker compose run docker compose exec
前提 サービスが停止中でも実行可 サービスが起動中でなければ不可
コンテナ 新しいコンテナを作成して実行 既存コンテナ内でコマンドを実行
典型的な用途 マイグレーション・テスト・シード 起動中サービスのデバッグ・調査
ポートの公開 デフォルトは公開しない(--service-portsで開く) 既存コンテナの設定に従う
判断の目安は「そのサービスが今起動しているかどうか」です。

・サービスが停止中でマイグレーションやテストを実行したい → docker compose run --rm
・サービスが起動中でログ確認・シェル接続・設定確認をしたい → docker compose exec

「Service "app" is not running」というエラーが出た場合は、docker compose exec を使っているのにサービスが停止している状態です。1回きりの処理には docker compose run --rm を使ってください。

4. 「エラーが出て実行できない」時の対処法

no such service エラー(runとexecで共通)

サービス名をタイポすると次のようなエラーが出ます。

# エラー例(出力例・環境により異なります) No service found with name "aap" # 対処: compose.yml の services: 配下のキーを確認する docker compose config --services

docker compose config --services を実行すると、compose.yml に定義されているサービス名の一覧が出力されます。タイポがないか照合してください。

container not running エラー(execのみ)

起動していないコンテナに対してexecを実行すると次のエラーが出ます。

# エラー例(出力例・環境により異なります) Error response from daemon: container not running # コンテナの稼働状況を確認する docker compose ps # コンテナが停止していた場合はrunを使う docker compose run --rm サービス名 コマンド

execはコンテナが稼働中でなければ実行できません。停止しているサービスに対してワンオフ処理を実行したいときは、execではなくrunを使います。

ポートバインドが必要な場合(runのデフォルト動作)

docker compose runはデフォルトでポートバインドを行いません。ホストからコンテナのポートに接続する必要があるときは --service-ports を付けます。

# ポートバインドを有効にしてrunする docker compose run --rm --service-ports web bash

5. 停止済みコンテナが溜まった場合のクリーンアップ

--rm なしで繰り返し実行すると停止済みコンテナが蓄積します。docker compose rm -f でまとめて削除できます。

# プロジェクトに紐付く停止済みコンテナを削除 $ docker compose rm -f # 全停止コンテナをまとめて削除(他プロジェクトも含む) $ docker container prune # 停止済みコンテナを確認してから削除 $ docker ps -a --filter status=exited

今後は --rm を常に付ける習慣をつけるか、シェルエイリアスに組み込むと運用が楽になります。

depends_onとヘルスチェックを使う時の注意点

ヘルスチェックは「起動の遅延」であり「接続の保証」ではありません。(要注意)

ヘルスチェック通過後も一時的にDBへの接続が失敗するケース(高負荷・ネットワーク瞬断)は現場で実際に起こります。アプリ側にも接続リトライ処理を実装するのが運用の鉄則です。

PythonのSQLAlchemyであれば pool_pre_ping=True を設定することで、使用前に接続の生存確認が行われます。

# SQLAlchemy の接続リトライ設定例 from sqlalchemy import create_engine engine = create_engine( "postgresql://appuser:secret@db:5432/appdb", pool_pre_ping=True, # 使用前に接続確認 pool_recycle=3600, # 1時間で接続を再作成 connect_args={"connect_timeout": 10} )

また、Compose v2.20以降では depends_on の required フィールドで依存サービスが起動できなかった場合の挙動を制御できます。required: false を指定すると、依存先が起動失敗してもアプリを起動させることができます。Redisが落ちてもアプリ本体は動作させたいといったグレースフルデグレードの設計に活用します。

services: app: image: myapp:latest depends_on: db: condition: service_healthy required: true # dbがないと起動しない(デフォルト) redis: condition: service_healthy required: false # redisが起動できなくてもappは起動する db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine healthcheck: test: ["CMD-SHELL", "redis-cli ping | grep PONG"] interval: 5s timeout: 3s retries: 3

profilesありのサービスをdepends_onで参照する場合の注意点

Docker Compose の profiles 機能でサービスを条件起動している場合、そのサービスへの depends_on に注意が必要です。profiles が付いたサービス(例:adminer・Mailpit等の開発ツール)をprofilesなしのサービス(app本体)が depends_on で参照すると、対応するプロファイルを有効化しない限り起動エラーになります。

# NG: profilesなしのappがprofiles付きのmailpitをdepends_onで参照 services: app: image: myapp:latest depends_on: mailpit: condition: service_healthy # devプロファイル未指定だと起動不能 mailpit: image: axllent/mailpit profiles: - dev # OK: 環境変数をデフォルト値付きにして疎結合にする services: app: image: myapp:latest environment: - MAIL_HOST=localhost - MAIL_PORT=25 # mailpit への depends_on は書かない mailpit: image: axllent/mailpit profiles: - dev

プロファイルを使って環境別にサービスを切り替える構成では、常時起動のサービスからプロファイル付きサービスへの依存関係を持たせないのが設計の基本です。

profilesのトラブルシュート

・profiles名の大文字・小文字が一致しているか確認する:profilesはケースセンシティブです。compose.ymlに profiles: [Dev] と書いて --profile dev で指定すると一致しません。すべて小文字に統一するのがベストプラクティスです
・profiles付きサービスが docker compose up で起動しない:これはバグではなく正常動作です。docker compose --profile dev up -d のようにプロファイルを明示してください
・docker compose ps で profiles サービスが表示されない:docker compose ps はプロファイルを指定しない限り起動中のコンテナのみを表示します。docker compose --profile dev ps と指定することでdevプロファイルのサービスを含めた状態を確認できます
・「no such service: xxx」が出た場合:--profile を指定せずにprofilesサービスを個別起動しようとしている。docker compose --profile dev up adminer -d のようにプロファイルを先に指定してから起動します
・「service xxx is not defined」が出た場合:デフォルトサービスからprofilesサービスへの depends_on が原因のことがほとんど。depends_onを外し、アプリ側の接続先を環境変数で切り替える設計に変更します
・docker compose config で特定サービスの定義を確認する:docker compose --profile dev config | grep -A 5 adminer のようにプロファイルを指定してからconfigを実行すると、変数展開・ファイル合成後の最終的な定義を確認できます
・予期しないサービスが起動・停止する場合はCOMPOSE_PROFILESを確認する:echo $COMPOSE_PROFILES で現在の設定値を確認し、意図しない値があれば unset COMPOSE_PROFILES で解除します
・docker compose ps --all で全サービスの状態を把握する:--all を付けると停止中のコンテナも含めて一覧表示されます
・全サービスを一括起動したい場合は --profile "*" を使う:docker compose --profile "*" up(Compose v2.24以降)。それ以前のバージョンでは定義済みプロファイルをすべて COMPOSE_PROFILES にカンマ区切りで並べて代替します

# 設定を確認するコマンド一覧 $ echo $COMPOSE_PROFILES # 現在の環境変数を確認 $ docker compose config --profiles # 定義済みプロファイル一覧を確認(v2.15以降) $ docker compose config # YAML展開後の定義を確認 $ docker compose --profile dev config | grep -A 5 adminer # devプロファイルでのadminer定義を確認 $ docker compose --profile dev ps --all # devプロファイル込みで全サービス確認 # CIや本番デプロイスクリプトでCOMPOSE_PROFILESの影響を受けないようにする $ env -u COMPOSE_PROFILES docker compose up -d

CI/CDパイプラインや本番デプロイスクリプトでは、env -u COMPOSE_PROFILES docker compose up -d のようにシェル変数の引き継ぎを明示的に排除するのが安全です。

deploy.replicas でスケールした場合のヘルスチェック

deploy.replicas で同一サービスのコンテナを複数起動する場合、ヘルスチェックは各コンテナに対して独立して実行されます。Docker Compose v2.x は Swarm なしのローカル環境でも deploy.replicas が有効です。depends_on: condition: service_healthy は依存先サービスの起動したコンテナが healthy になった時点で満たされます。

services: db: image: postgres:16 deploy: replicas: 2 # 2台起動。各コンテナにヘルスチェックが独立実行される resources: limits: cpus: "0.50" memory: 512M # 2台 × 512MB = 最大1GB消費になるため注意 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s start_period: 15s # 複数起動時は初期化の競合を考慮して長めに設定 retries: 5 app: image: myapp:latest depends_on: db: condition: service_healthy

複数レプリカを同時起動すると初期化が重なりやすいため、start_period を通常より長めに設定してください。また deploy.resources.limits でコンテナごとのCPU・メモリ上限を設定しておくと、レプリカ増加時のホストリソース過消費を防げます。

・limits.cpus:コンテナが使用できるCPUコア数の上限(例: "0.50" = 0.5コア)
・limits.memory:コンテナが使用できるメモリの上限(超えると OOM Kill)
・reservations.memory:スケジューリング時に確保するメモリ量の下限

replicas: 3・memory: 256M の場合、3コンテナ合計で最大768MBを消費します。スケール設定を調整する前に free -h でホストの空きメモリを確認してください。

本記事のまとめ

Docker Compose の起動順序制御について、depends_on の3種類のconditionとHEALTHCHECKの設定方法、profilesによる環境別サービス管理、docker compose runによる1回きり実行パターンを解説しました。

やりたいこと 設定・コマンド
DBの準備完了を待ってアプリを起動する condition: service_healthy
DBのヘルスチェックを定義する Composefile の healthcheck セクション
initコンテナの完了を待つ condition: service_completed_successfully
ヘルスチェック状態を確認する docker inspect --format='{{json .State.Health}}'
healthcheckの進捗をリアルタイム確認する docker events --filter event=health_status
healthcheck設定の解釈を確認する docker compose config
タイムアウト(ExitCode 124)を調べる --timeoutの値を延ばして再確認する
接続リトライをアプリ側で対処する SQLAlchemy の pool_pre_ping=True 等
依存サービスが落ちてもアプリを起動する required: false(Compose v2.20以降)
開発ツールを本番に混入させない profiles: [dev] でサービスを環境別に分離する
devツールのポートを外部公開しない ports に 127.0.0.1:8080:8080 と指定する
SMTP接続先を環境変数で切り替える SMTP_HOST: ${SMTP_HOST:-mailpit} でデフォルト値指定
監視スタックを条件起動する profiles: [monitoring] でPrometheus等を分離する
シードデータ投入を一回限り実行する profiles: [init] + restart: "no"
テスト専用DBコンテナを分離する profiles: [test] で db-test を独立定義する
profilesサービスへのdepends_onを避ける 環境変数のデフォルト値で疎結合に設計する
profilesサービス間で依存関係を持たせる 依存するサービス間で同じprofiles名を共有する
起動するプロファイルを環境変数で制御する COMPOSE_PROFILES=dev(環境変数または .env)
本番サーバーのプロファイルをOS設定で固定する /etc/environment または systemd の EnvironmentFile
定義済みプロファイル一覧を確認する docker compose config --profiles(Compose v2.15以降)
profilesなしで起動するサービスを確認する docker compose config --services
CI/CDでCOMPOSE_PROFILESの引き継ぎを防ぐ env -u COMPOSE_PROFILES docker compose up -d
全サービスを一括起動する docker compose --profile "*" up(Compose v2.24以降)
親イメージのHEALTHCHECKを無効化する HEALTHCHECK NONE
マイグレーション・テスト・シードを1回きり実行する docker compose run --rm サービス名 コマンド
実行後にコンテナを自動削除する docker compose run --rm
依存サービスを起動せずに実行する docker compose run --rm --no-deps サービス名 コマンド
CIからマイグレーションだけを一時起動する docker compose --profile ci run --rm migrate
CIでtestプロファイルのテストを一括実行する docker compose --profile test up --abort-on-container-exit --exit-code-from app-test
停止中サービスのコマンドを即興で実行する docker compose run --rm(exec は起動中のみ)
起動中コンテナにシェルで接続してデバッグする docker compose exec -it サービス名 bash
起動中コンテナで設定ファイルを確認する docker compose exec サービス名 cat /path/to/config
ENTRYPOINTを無視してbashを起動する docker compose run --rm --entrypoint bash サービス名
runでポートバインドを有効にする docker compose run --rm --service-ports サービス名 コマンド
停止済みコンテナをまとめて削除する docker compose rm -f
レプリカ数を宣言して複数コンテナを起動する deploy.replicas: 3(Compose v2.x はSwarm不要)
スケール時にコンテナごとのリソース上限を設定する deploy.resources.limits(cpus / memory を指定する)
debug プロファイルでネットワーク診断コンテナを起動する debug プロファイル + netshoot + network_mode: "service:app"
環境別 .env ファイルでプロファイルを自動切り替える .env.dev / .env.production を cp .env して使う
includeとextendsとoverride ファイルの違いを把握する include=ファイル全体取り込み、extends=サービス継承、override=差分上書き
compose.yml をファイル単位で分割して管理する include: [infra/compose.yaml, ...](Compose v2.20以降)
インクルード先のサービス基準パスを指定する project_directory: ./infra
インクルード後の全サービス一覧を確認する docker compose config --services
分割後も depends_on・service_healthy を使う インクルード先のサービス名をそのまま参照できる
・ヘルスチェックは「DBが接続を受け付けているか」を確認するコマンドを test に指定する
・start_period で起動猶予時間を設ける(MySQLは30s以上、PostgreSQLは10s程度を目安に調整する)
・MySQLは -h 127.0.0.1 を指定する(-h localhost はUnixソケット経由になる場合がある)
・curl が使えないイメージは nc または専用CLI(pg_isready・mysqladmin ping・redis-cli ping)で代替する
・ヘルスチェック通過はあくまで「起動の遅延」。アプリ側のリトライ処理も必ず実装する
・profiles: [dev] で開発ツールを本番から分離し、誤起動リスクをゼロにする
・devツールのポートは 127.0.0.1:ホスト:コンテナ でlocalhostバインドし外部公開を防ぐ
・Mailpit等のSMTPモックと環境変数デフォルト値でメール送信設定を環境ごとに自動切り替えする
・initプロファイルで一回限りのシード処理を分離し、常駐コンテナに混ぜない
・testプロファイルで専用DBコンテナ(db-test)を分離し、本番DBに影響を与えずCIテストを実行する
・profilesで条件起動するサービスへの depends_on は疎結合設計に切り替える
・profilesサービス間の depends_on は同じprofiles名を共有して設計する
・COMPOSE_PROFILES 環境変数または .env ファイルで起動プロファイルを環境ごとに自動切り替えする
・本番サーバーは /etc/environment や systemd の EnvironmentFile で COMPOSE_PROFILES= を固定管理できる
・docker compose config --profiles で定義済みプロファイル名を一覧確認できる(Compose v2.15以降)
・docker compose run --rm でマイグレーション・テスト・シードを手軽にワンショット実行できる
・GitHub Actions から docker compose --profile ci run --rm migrate でCIマイグレーションを一時起動できる
・--profile test up --abort-on-container-exit --exit-code-from でCIテスト全体の起動・実行・停止を1コマンドで制御できる
・docker compose run と docker compose exec の使い分けは「サービスが停止中か起動中か」で判断する
・docker compose exec -it サービス名 bash で起動中コンテナにシェルセッションを開いてデバッグできる
・docker compose run --rm --entrypoint bash サービス名 でENTRYPOINTを無視してbashを起動できる
・「no such service」はサービス名のタイポ確認、「container not running」は exec から run への切り替えで対処する
・docker compose run --rm --service-ports でrunコマンドでもポートバインドを有効にできる
・deploy.replicas でスケールした各レプリカには独立してヘルスチェックが実行される
・deploy.resources.limits でコンテナごとのCPU・メモリ上限を設定し、レプリカ増加時のリソース過消費を防ぐ
・debug プロファイルで netshoot コンテナを起動し、app コンテナのネットワーク名前空間を共有してネットワーク診断できる(本番コンテナへの影響ゼロ)
・.env.dev / .env.production のように環境別 .env を用意して cp するだけで COMPOSE_PROFILES を自動切り替えできる
・include ディレクティブで compose.yml をファイル単位で分割し、1プロジェクトとして管理できる(Compose v2.20以降)
・include はファイル全体を取り込む機能(extends はサービス定義の継承、override ファイルはローカル差分の上書きと目的が異なる)
・include で分割してもサービス名は共通なため、depends_on と service_healthy による起動順序制御はそのまま機能する
・docker compose config --services でインクルードを含む全サービス一覧を確認する

次に読む記事

・docker compose runで1回きりのサービスを実行する方法|マイグレーション・シード・テスト実行の実践パターン
・DockerのENV・ARG・env_fileを正しく使う設計|ビルド時と実行時の値の渡し方とsecrets
・DockerfileのENTRYPOINTとCMDを正しく使い分ける方法|シェル形式・exec形式の設計指針
・Docker Compose入門|複数コンテナでWordPress環境を構築するハンズオン

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Docker Composeの起動順序設計からコンテナ本番運用まで体系的に学べるDocker講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。