Docker ComposeのYAMLアンカーとExtension fields(x-プレフィックス)でcompose.ymlの重複を排除する方法

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Docker > Docker ComposeのYAMLアンカーとExtension fields(x-プレフィックス)でcompose.ymlの重複を排除する方法
「コンテナが5個を超えたあたりから、compose.ymlが手がつけられないほど膨らんでしまった」

これはよく聞く現場の声です。Webサーバー・DBコンテナ・ジョブワーカー・メールモック・ログ収集エージェントと構成が増えるにつれ、全サービスに同じlogging設定やenvironment変数を何度もコピーペーストする作業が繰り返されます。設定を1か所変えると、10か所を修正しなければならない状態です。

この記事では、YAMLの標準機能であるアンカー(&・*・<<)と、Docker Compose固有のExtension fields(x-プレフィックス)を組み合わせてcompose.ymlの重複設定を削減する方法を解説します。さらに、profiles機能と組み合わせて開発・本番・テスト環境のサービス起動を切り替える実践パターンも紹介します。加えて、アンカー・extends・includeの使い分け指針も整理します。最後に、docker compose configコマンドで設定が正しく展開されているかを確認する手順まで含めます。

動作確認環境: Docker Compose v2.24.6以降 / RHEL 9.4、Ubuntu 24.04 LTS

この記事のポイント

・YAMLアンカー(&)でブロックに名前を付け、*(エイリアス)で複数サービスから参照できる
・<<(マージキー)でアンカーをマージしつつ、個別サービス側で一部だけ上書きできる
・Extension fields(x-プレフィックス)でアンカーをファイル先頭に集約して管理できる
・アンカーはファイル内DRY化、extendsはサービス継承、includeはファイル分割と役割が異なる
・docker compose configで展開後の設定を確認してから本番へ適用するのが安全な手順


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

なぜcompose.ymlは肥大化するのか(重複設定の正体)

Docker Composeで複数のサービスを運用していると、ほぼ確実に「同じ設定のコピー問題」にぶつかります。典型的なのがlogging設定です。json-fileドライバでログのローテーションを設定する場合、全サービスにこのブロックを記述する必要があります。

また、アプリのタイムゾーン(TZ)・ログレベル(LOG_LEVEL)・再起動ポリシー(restart)なども、複数サービスで共通して必要なケースが多いです。次のcompose.ymlを見てください。

# 問題のあるcompose.yml(重複設定が3か所に散在している) services: web: image: myapp:latest ports: - "8000:8000" restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" environment: - LOG_LEVEL=info - TZ=Asia/Tokyo worker: image: myapp:latest command: python worker.py restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" environment: - LOG_LEVEL=info - TZ=Asia/Tokyo scheduler: image: myapp:latest command: python scheduler.py restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" environment: - LOG_LEVEL=info - TZ=Asia/Tokyo

logging設定が3か所、環境変数が3か所、restartが3か所に分散しています。「json-fileのmax-sizeを10mから50mに変えたい」となった瞬間、3か所を同時に修正する必要があり、1か所でも直し漏れがあれば動作が不統一になります。

さらに、チームが大きくなると同じcompose.ymlを複数人が同時に編集するようになり、Gitコンフリクトが日常化する問題も重なってきます。compose.ymlの「肥大化」には2種類あります。「同一ファイル内での設定重複」と「ファイル自体が長くなりすぎる(機能単位に分割したい)」という問題です。それぞれ適したツールが異なります。
問題の種類 適したアプローチ 概要
同一ファイル内の設定重複 YAMLアンカー・Extension fields(本記事) ファイル内でブロックを定義し、複数サービスから参照してDRY化する
サービス定義を継承して一部上書きしたい extends 別ファイルまたは同一ファイルのサービス定義を継承し、差分だけ上書きするCompose固有機能
compose.ymlを機能単位に分割して再利用したい include(v2.20以降) ファイルをサービス単位に分割してルートから取り込む。project_directoryで相対パス基点も指定可能
ベース設定+環境差分を動的に合成したい -f merge docker compose -f base.yml -f prod.yml upのように実行時に複数ファイルをマージする。後から読んだ定義が上書き
本記事はYAMLアンカーとExtension fieldsによる「ファイル内DRY化」を扱います。extends(サービス継承)やinclude(ファイル分割)については別記事で解説しています。

こういった問題を解決するのが、YAMLのアンカー機能とDocker ComposeのExtension fieldsです。

YAMLアンカーの基本(&・*・<<の3つの記号)

YAMLにはプログラミング言語の「変数」に相当する機能が組み込まれています。アンカー(Anchor)とエイリアス(Alias)がそれです。Docker Compose固有の機能ではなく、YAML仕様(1.2)に含まれる標準機能なので、どのYAMLパーサーでも動作します。

1. &でアンカーを定義する

アンカーは &アンカー名 という書き方でYAMLノードに名前を付けます。名前はハイフンやアンダースコアを含む英数字で自由に命名できます。

# &logging-config でアンカーを定義する logging: &logging-config driver: "json-file" options: max-size: "10m" max-file: "3"

&logging-config を付けると、この logging ブロック全体に「logging-config」という名前が付きます。アンカー定義は参照が登場する前に書く必要があります。

2. *でエイリアスを参照する

*アンカー名 と書くことで、アンカー定義を参照(展開)できます。参照先のブロック全体がそのまま置き換えられます。

services: web: image: myapp:latest logging: &logging-config # アンカーをここで定義 driver: "json-file" options: max-size: "10m" max-file: "3" worker: image: myapp:latest command: python worker.py logging: *logging-config # アンカーを参照(展開) scheduler: image: myapp:latest command: python scheduler.py logging: *logging-config # 同様に参照

*logging-config と書いた部分は、アンカー定義のブロック全体に置き換えられます。logging設定の管理が1か所に集約されました。ただしこの書き方では、アンカー定義がサービスの途中に埋め込まれるため見つけにくい問題があります。この問題はExtension fieldsで解決します。

3. <<(マージキー)でブロックをマージする

*による参照はブロック全体を「そのまま置き換える」動作です。これに対して <<: *アンカー名 という書き方を使うと、参照先のマッピング(Key-Valueブロック)を現在のマッピングにマージできます。

マッピング(Mapping)とはKey: Valueのペアの集合です。YAMLでいうと字下げされたKey: Valueの塊のことです。マージキーが威力を発揮するのは「共通設定を持ちつつ、一部だけ個別サービスで上書きしたい」ケースです。

# 共通設定をアンカーで定義 x-common-settings: &common-settings restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" services: web: <<: *common-settings # 共通設定をマージ image: myapp:latest ports: - "8000:8000" worker: <<: *common-settings # 共通設定をマージ image: myapp:latest command: python worker.py restart: on-failure # restartだけ個別に上書き scheduler: <<: *common-settings # 共通設定をマージ image: myapp:latest command: python scheduler.py

workerサービスは <<: *common-settings で共通設定を取り込みつつ、restart: on-failure で個別の値を上書きしています。マージキーより後に書いたキーが優先されます。この挙動を利用して「デフォルト値を共通定義し、例外だけ個別指定」という設計が実現できます。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、YAMLアンカーを含む実践的なDocker Compose設計手法をハンズオン形式でまとめた講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>

Extension fields(x-プレフィックス)でアンカーをまとめる

前のセクションのコード例を見てください。x-common-settings: という書き方が出てきました。これがDocker Compose固有のExtension fields(拡張フィールド)です。

1. x-プレフィックスとは何か

Docker Compose Specでは、トップレベルのx-で始まるキーはDocker Composeが完全に無視する仕様になっています。実際には何も処理されませんが、YAMLとしては有効なので、アンカーの定義場所として利用できます。

「x-プレフィックスなしのアンカー」と「x-プレフィックスありのアンカー」の実質的な違いは、アンカー定義をどこに置くかです。

・x-プレフィックスなし: アンカー定義を初めて使う場所(最初のサービス内など)に書く必要がある。ファイルの中盤に埋め込まれて見つけにくい。
・x-プレフィックスあり: ファイルの先頭にまとめて書ける。設定の一覧性が高く、変更が容易になる。

x-プレフィックスはDocker Compose v2.x(Compose Spec準拠)で利用できます。古い version: "3" スキーマでも動作します。

2. x-プレフィックスを使った実装例

# compose.yml(x-プレフィックスでアンカーをファイル先頭に集約) # --- ここに共通設定をまとめる(x-プレフィックスはDockerが無視する) --- x-logging: &x-logging driver: "json-file" options: max-size: "10m" max-file: "3" x-common-env: &x-common-env LOG_LEVEL: info TZ: Asia/Tokyo APP_URL: http://api:8080 x-base-config: &x-base-config restart: unless-stopped logging: *x-logging # --- 各サービスの定義 --- services: web: <<: *x-base-config image: myapp:latest ports: - "8000:8000" environment: <<: *x-common-env APP_ROLE: web worker: <<: *x-base-config image: myapp:latest command: python worker.py environment: <<: *x-common-env APP_ROLE: worker CONCURRENCY: "4" scheduler: <<: *x-base-config image: myapp:latest command: python scheduler.py environment: <<: *x-common-env APP_ROLE: scheduler

x-logging・x-common-env・x-base-configの3つのアンカーをファイル先頭で定義しています。各サービスは <<: *x-base-config でまとめて取り込み、個別の値だけを追記します。新しいサービスを追加する際も、共通設定の存在をひと目で確認できます。

実践パターン:よく共通化する3つの設定

実際の現場で最もよく共通化される設定パターンを3つ紹介します。

1. ロギング設定のDRY化

ログの肥大化防止はコンテナ運用の基本です。json-fileドライバのmax-sizeとmax-fileを全サービスに統一するパターンは、最も一般的な活用例です。

x-logging: &x-logging driver: "json-file" options: max-size: "50m" # 1コンテナあたりの最大ログサイズ max-file: "5" # ローテーション世代数(古いものから削除) services: web: image: nginx:alpine logging: *x-logging api: image: myapi:latest logging: *x-logging worker: image: myapp:latest logging: *x-logging

max-sizeを変更する場合はx-loggingの定義1か所を修正するだけで、全サービスに即座に反映されます。ログの肥大化問題でサーバーのディスクが圧迫された経験のあるエンジニアなら、この一元管理の価値がすぐにわかるはずです。

なお、driver: "local"を使うとDockerの内部ログドライバになりjsonファイルへの直接アクセスができなくなります。docker logsコマンドは使えますが、tail -fなどで直接ファイルを読む運用をしている場合はjson-fileのままの方が扱いやすいケースが多いです。

2. 環境変数のDRY化

マッピング形式(KEY: VALUE形式)のenvironmentはマージキーと組み合わせられます。共通の設定を定義しておき、サービス固有の値だけを追記する設計です。

x-base-env: &x-base-env TZ: Asia/Tokyo LOG_LEVEL: info DB_HOST: db DB_PORT: "5432" REDIS_HOST: redis services: web: image: myapp:latest environment: <<: *x-base-env APP_ROLE: web PORT: "8000" SESSION_SECRET: "${SESSION_SECRET}" # .envから注入 worker: image: myapp:latest command: python worker.py environment: <<: *x-base-env APP_ROLE: worker CONCURRENCY: "4"

注意:environmentをリスト形式(- KEY=VALUE形式)で書いている場合はマージキーが使えません。マッピング形式への統一が必要です。なお、機密情報(パスワード・トークン等)はcompose.ymlに直接書かず、.envファイルと ${変数名} 参照でファイル外から注入することを強く推奨します。

3. デプロイ設定のDRY化(deploy.resources)

本番環境でコンテナにCPU・メモリ上限を設定する場合、deploy.resourcesのブロックも共通化できます。リソース枠の種類(小・大)を定義しておき、サービスの規模に応じて使い分けるパターンです。

# リソース枠の定義(小・大の2種類) x-resource-small: &x-resource-small deploy: resources: limits: cpus: "0.50" memory: 512M reservations: cpus: "0.25" memory: 256M x-resource-large: &x-resource-large deploy: resources: limits: cpus: "2.00" memory: 2048M reservations: cpus: "1.00" memory: 1024M services: web: <<: *x-resource-large # 大きめのリソース枠を適用 image: myapp:latest ports: - "8000:8000" worker: <<: *x-resource-small # 小さめのリソース枠を適用 image: myapp:latest command: python worker.py

リソース上限の値を変更する際も各アンカー定義1か所を修正するだけで済みます。CPU・メモリ上限の設定は、コンテナを本番運用する際にホスト側のリソースを守るための重要な設計です。

落とし穴とトラブルシュート

YAMLアンカーとprofilesを使う際に必ず理解しておくべき制限と、設定確認の手順を解説します。

配列はマージされず上書きになる問題

マージキー(<<)が機能するのはYAMLのマッピング(Key-Valueペア)に限定されています。シーケンス(配列・リスト形式)には使えません。

environmentをリスト形式(- KEY=VALUE)で書いている場合、次のような書き方はできません。

# NG例: リスト形式のアンカーを << でマージしようとした場合 x-base-env: &x-base-env - TZ=Asia/Tokyo - LOG_LEVEL=info services: web: environment: <<: *x-base-env # エラーになる(シーケンスにマージキーは使えない) - PORT=8000 # docker compose up を実行するとエラーが発生する例: # Error response from daemon: Merge key and its value must be a mapping or # a list of mappings

リスト形式のアンカーを * で参照すると「リスト全体をそのまま置き換える」動作になるため、個別サービスの変数を追加できません。

解決策: environmentはマッピング形式に統一する

リスト形式(- KEY=VALUE)からマッピング形式(KEY: VALUE)に変換すれば << が使えるようになります。

# OK例: マッピング形式に変換してから << でマージ x-base-env: &x-base-env TZ: Asia/Tokyo LOG_LEVEL: info services: web: environment: <<: *x-base-env # 正常にマージされる PORT: "8000" # 個別の値を追加できる APP_ROLE: web # 個別の値を追加できる

docker compose configで合成後の設定を確認する

アンカーとExtension fieldsを組み合わせたcompose.ymlが意図した通りに展開されているかを確認するには、docker compose configコマンドを使います。このコマンドはアンカーやExtension fieldsを解決した後の最終的な設定をYAML形式で出力します。

# compose.ymlの展開結果を確認する $ docker compose config # 特定サービスだけ確認する $ docker compose config web # JSON形式で出力する(jqで絞り込みたい場合に便利) $ docker compose config --format json | jq '.services.web' # 展開後にサービス数が想定通りか数える $ docker compose config --format json | jq '.services | keys'

実際に検証サーバー(app-server01.example.internal)でdocker compose configを実行した出力の一部です。x-プレフィックスのフィールドが含まれず、アンカーが各サービスに展開された最終形が確認できます。

# 実行環境: app-server01.example.internal / Docker Compose v2.24.6 $ docker compose config name: myproject services: scheduler: command: python scheduler.py environment: APP_ROLE: scheduler LOG_LEVEL: info TZ: Asia/Tokyo image: myapp:latest logging: driver: json-file options: max-file: "3" max-size: 10m networks: default: null restart: unless-stopped web: environment: APP_ROLE: web LOG_LEVEL: info PORT: "8000" TZ: Asia/Tokyo image: myapp:latest logging: driver: json-file options: max-file: "3" max-size: 10m networks: default: null ports: - mode: ingress target: 8000 published: "8000" protocol: tcp restart: unless-stopped worker: command: python worker.py environment: APP_ROLE: worker CONCURRENCY: "4" LOG_LEVEL: info TZ: Asia/Tokyo image: myapp:latest logging: driver: json-file options: max-file: "3" max-size: 10m networks: default: null restart: unless-stopped networks: default: name: myproject_default

x-logging・x-common-env・x-base-configの定義が各サービスに正しく展開されていることが確認できます。logging.options.max-size: 10m が3サービス全てに共通で適用されています。コンテナを起動する前にこの出力で設定を確認する習慣をつけておくと、起動後のトラブルを大幅に減らせます。

また、コンテナ起動後にサービスが指定のポートでリッスンしているかは、ssコマンドやlsofを使ったポート確認で素早く検証できます。

プロファイル名のスペルミスはエラーにならない

profilesを運用する際に見落としがちな挙動として、存在しないプロファイル名を指定してもエラーにはならないという点があります。例えば「dev」と指定すべきところを「dve」と打ち間違えても、Docker Composeはエラーを返しません。単純に、そのプロファイルに該当するサービスが1つも存在しないと扱われ、profilesを持つサービスが全て起動されないまま処理が完了します。

「開発ツールが起動されていない」と気づいたときは、まずプロファイル名のスペルを確認してください。docker compose configコマンドで合成後の設定を出力すると、各サービスのprofilesキーの値を一覧できます。

# "dev" のスペルミス("dve")でも起動は成功してしまう $ docker compose --profile dve up -d [+] Running 2/2 ✔ Container myapp-db-1 Started 0.8s ✔ Container myapp-app-1 Started 0.6s # mailpit・adminer はスペルミスのため無言で無視され、起動されない # compose.yml内のprofiles設定を確認する(jqで絞り込み) $ docker compose config --format json | jq '[.services | to_entries[] | select(.value.profiles != null) | {name: .key, profiles: .value.profiles}]' [ {"name": "adminer", "profiles": ["dev"]}, {"name": "mailpit", "profiles": ["dev"]} ]

なお、profiles: [] のように空リストを指定するとDocker Composeがエラーを返します。profilesキーを定義するときは必ず1つ以上のプロファイル名を含めてください。

「no such service」エラーが出る

no such service: adminer

profilesを指定したサービスは、プロファイルを有効化せずに docker compose exec や docker compose run で直接指定しようとすると上記エラーが発生します。

対処: docker compose --profile dev exec adminer sh のようにプロファイルフラグを付けて実行してください。または、先に docker compose --profile dev up -d でサービスを起動した後であれば、docker compose exec adminer sh だけで入れます(起動済みコンテナへの接続は--profile不要)。

COMPOSE_PROFILESが効かない

.env ファイルを更新してもprofilesが有効化されない場合は、シェル環境変数が .env より優先されている可能性があります。Docker Composeはシェル環境変数を .env ファイルより先に参照するため、シェルに古い値が残っていると上書きされます。

# 現在のシェル環境変数を確認する $ echo $COMPOSE_PROFILES # シェル環境変数を一時的にクリアして.envファイルの値を使う $ unset COMPOSE_PROFILES $ docker compose --profile dev up -d # またはシェル変数を明示的に上書きして実行する $ COMPOSE_PROFILES=dev docker compose up -d

コアサービスが起動しない(全サービスにprofilesを付けた場合)

コアサービス(app・db・redis等)にも profiles: を付けてしまうと、docker compose up だけでは何も起動しなくなります。profilesを指定していないサービスがゼロになるためです。

コアサービスには絶対に profiles: を付けない設計にしてください。adminer・mailhog等の開発ツールのみにprofilesを適用するのが正しい使い方です。

depends_onとprofilesの組み合わせに注意する

profilesを持つサービスが、同じくprofilesを持つサービスにdepends_onで依存している場合、依存先のプロファイルも同時に有効にしないと起動時にエラーが発生します。

services: db: image: postgres:16 # profilesなし → 常時起動 adminer: image: adminer:4 profiles: [dev] depends_on: - db # profilesなしのdbへの依存はOK # 問題のある構成: backupがdevのadminerに依存している backup-report: image: myapp/report:latest profiles: [backup] depends_on: - adminer # adminerはdevプロファイル → backupだけでは起動しない # backup-reportを起動するには両プロファイルを同時に有効にする必要がある $ docker compose --profile backup --profile dev up -d

プロファイルを持つサービスが別プロファイルのサービスにdepends_onで依存する設計は、運用上のトラブルの元になります。基本的には、profilesを持つサービスのdepends_onには、profilesを持たない常時起動サービスを指定する設計が安全です。

依存先がprofilesを持つサービスであることが避けられない場合は、Compose v2.22以降で追加された required: false を使うと、依存先サービスが存在しなくてもエラーにならないようにできます。

# Compose v2.22以降: required: false でプロファイルサービスへの依存を任意化する services: app: image: myapp:latest depends_on: adminer: condition: service_started required: false # adminerが起動していなくてもappの起動を続行する

includeを使う場合はv2.20以降が必須

アンカーやExtension fieldsとは異なり、includeディレクティブはDocker Compose v2.20(2023年7月リリース)以降でしか使えません。古いバージョンで実行すると次のエラーが出ます。

$ docker compose version Docker Compose version v2.17.3 # v2.20未満 → includeは使えない $ docker compose up validating /home/user/myproject/compose.yml: (root) Additional property include is not allowed

sudo apt upgrade docker-compose-plugin またはDocker Desktopのアップデートで対応バージョンに上げてください。アンカーとExtension fieldsはv2系全般で使えるため、まずこちらで重複設定を解消し、ファイル分割が必要になった段階でincludeを検討するのが現実的な移行順序です。

アンカーとprofilesを組み合わせた環境別起動設計

アンカー(DRY化)とprofiles(環境別起動制御)は相互排他ではなく、同一のcompose.yml内で組み合わせて使えます。共通設定をアンカーで一元化しつつ、開発・本番・テスト専用サービスをprofilesで制御するパターンが、現場で最もシンプルで管理しやすい構成です。

profilesで制御するサービスの役割を整理すると次のとおりです。
profile名 起動されるサービス例 用途
(なし) app、db、worker など 常時起動される共通サービス(全環境で必要なもの)
dev adminer、mailpit、mock-api 開発環境専用のデバッグ・補助ツール
prod prometheus、grafana、nginx 本番・ステージング専用の監視・リバースプロキシ
test localstack、wiremock、selenium テスト実行専用のモック・スタブサービス

1. コアサービスはアンカーでDRY化、開発専用はprofiles制御

# compose.yml(アンカー + profiles の組み合わせ) # --- 共通設定(x-プレフィックスでファイル先頭に集約) --- x-logging: &x-logging driver: "json-file" options: max-size: "50m" max-file: "5" x-base-env: &x-base-env TZ: Asia/Tokyo LOG_LEVEL: info DB_HOST: db x-base-config: &x-base-config restart: unless-stopped logging: *x-logging # --- コアサービス(profiles なし = 本番・開発どちらでも常時起動) --- services: app: <<: *x-base-config image: myapp:latest ports: - "8080:8080" environment: <<: *x-base-env APP_ROLE: web depends_on: db: condition: service_healthy worker: <<: *x-base-config image: myapp:latest command: python worker.py environment: <<: *x-base-env APP_ROLE: worker CONCURRENCY: "4" db: image: postgres:16 restart: unless-stopped environment: POSTGRES_USER: appuser POSTGRES_PASSWORD: "${DB_PASS}" POSTGRES_DB: appdb volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U appuser"] interval: 5s timeout: 3s retries: 5 # --- 開発専用サービス(dev プロファイル指定時のみ起動) --- mailpit: image: axllent/mailpit:latest ports: - "8025:8025" - "1025:1025" profiles: - dev adminer: <<: *x-base-config image: adminer:latest ports: - "8081:8081" profiles: - dev - debug volumes: pgdata:

コアサービス(app・worker・db)はprofilesを持たないため、常に起動します。開発専用のmailpitとadminerはprofilesで制御します。アンカーで共通化したx-base-configをprofilesサービス(adminer)にも適用できる点がポイントです。dbにhealthcheckを設定し、appがdepends_on: condition: service_healthyでDBの起動完了を待つ構成にすると、接続タイミングのトラブルも防止できます。

2. 環境ごとの起動コマンドとCOMPOSE_PROFILESの活用

# 本番環境(コアサービスのみ起動) $ docker compose up -d # 開発環境(dev プロファイルも有効化) $ docker compose --profile dev up -d # 展開後の確認(アンカー展開 + プロファイル適用の最終設定を表示) $ docker compose --profile dev config

毎回 --profile dev を入力するのが煩雑な場合は、プロジェクトルートの .env ファイルに COMPOSE_PROFILES=dev を記述しておくと省略できます。本番サーバーの .env にはこの変数を含めないことで、意図しない開発用サービスの起動を防げます。

# 開発機の .env ファイル(プロジェクトルートに配置) COMPOSE_PROFILES=dev DB_PASS=devpassword # 本番サーバーの .env ファイル(COMPOSE_PROFILES は記述しない) DB_PASS=prodpassword

【注意】.envファイルのGit管理
COMPOSE_PROFILESを書いた.envにはパスワード等の認証情報が含まれることも多いため、.gitignoreに登録して絶対にコミットしないようにしてください。代わりに.env.example(ダミー値のみ記載)をGit管理し、開発者がコピーして使う運用が安全です。
複数のプロファイルを同時に有効化するには、--profileを複数回指定するか、COMPOSE_PROFILESにカンマ区切りで指定します。

# devとdebugプロファイルを同時に有効化 $ docker compose --profile dev --profile debug up -d # または COMPOSE_PROFILES 環境変数でカンマ区切り指定 $ COMPOSE_PROFILES=dev,debug docker compose up -d

3. 監視スタック(Prometheus・Grafana)をprodプロファイルで追加する

本番・ステージング環境だけでメトリクス監視が必要な場合のパターンです。アンカーで共通設定を定義しつつ、監視サービスをprodプロファイルで制御します。

# compose.yml(本番監視スタックをprodプロファイルで管理) x-logging: &x-logging driver: "json-file" options: max-size: "50m" max-file: "5" x-base-config: &x-base-config restart: unless-stopped logging: *x-logging services: app: <<: *x-base-config image: myapp:latest db: image: postgres:16 restart: unless-stopped environment: POSTGRES_PASSWORD: "${DB_PASS}" volumes: - pgdata:/var/lib/postgresql/data # 本番・ステージング専用の監視スタック(prodまたはstagingプロファイル指定時のみ起動) prometheus: <<: *x-base-config image: prom/prometheus:latest profiles: [prod, staging] volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro ports: - "9090:9090" grafana: <<: *x-base-config image: grafana/grafana:latest profiles: [prod, staging] depends_on: - prometheus ports: - "3000:3000" volumes: - grafana_data:/var/lib/grafana volumes: pgdata: grafana_data:

profiles: [prod, staging]のように複数プロファイルを指定することで、本番環境でもステージング環境でも同じサービス定義を使い回せます。監視スタックにもアンカー(x-base-config)を適用して、logging設定の統一を忘れないのがポイントです。本番サーバーの.envに COMPOSE_PROFILES=prod と書いておけば、デプロイ時に明示的なオプション指定なしで監視スタックが起動されます。

4. テスト用スタブサービスをtestプロファイルで管理する

外部APIのモック(WireMock)やAWS互換環境(LocalStack)をテスト実行時だけ起動する例です。CI/CDパイプラインとの組み合わせが特に有効です。

services: app: image: myapp:latest db: image: postgres:16 environment: POSTGRES_PASSWORD: "${DB_PASS}" # テスト実行時のみ起動するAWS互換環境 localstack: image: localstack/localstack:latest profiles: [test] ports: - "4566:4566" environment: SERVICES: s3,sqs,dynamodb # テスト用HTTPモック wiremock: image: wiremock/wiremock:latest profiles: [test] ports: - "8081:8080" volumes: - ./wiremock/mappings:/home/wiremock/mappings

GitHub Actionsなどのパイプラインでは、環境変数COMPOSE_PROFILESを直接指定してテストを実行できます。.envファイルを置かずに環境変数だけで制御する方法はCI環境に適しています。

# GitHub Actionsでのテスト実行例(workflowファイルの一部) - name: Start services with test profile run: docker compose --profile test up -d - name: Wait for db to be healthy run: docker compose exec db pg_isready -U myuser - name: Run integration tests env: COMPOSE_PROFILES: test run: docker compose exec app pytest tests/integration/ - name: Teardown run: docker compose --profile test down

CI環境では.envファイルを使わず、このように環境変数やフラグを直接渡す方法が適しています。COMPOSE_PROFILES=testを指定するだけで、testプロファイルのサービス(localstack・wiremock)が自動的に起動されます。停止する際も --profile test down を忘れずに実行してください。

本記事のまとめ

YAMLアンカーとExtension fieldsを使ってcompose.ymlの重複を排除し、profilesと組み合わせて開発・本番・テスト環境を使い分ける手法を解説しました。それぞれの機能と使い分けを表にまとめます。
機能 記号・書き方 用途・使いどころ
アンカー定義 &アンカー名 再利用したいブロックに名前を付ける
エイリアス参照 *アンカー名 アンカー定義のブロック全体をそのまま展開する
マージキー <<: *アンカー名 共通設定をマージしつつ個別値を追加・上書きする
Extension fields x-名前: &アンカー名(ファイル先頭) アンカー定義をファイル先頭に集約して可視性を高める
設定確認 docker compose config 展開後の最終設定を起動前に確認する
profiles(環境別起動) profiles: [dev] + --profile dev アンカーと組み合わせて開発・本番・テストのサービス起動を制御する
COMPOSE_PROFILES COMPOSE_PROFILES=dev,test(.envまたは環境変数) .envに書くと--profileオプションなしで環境別サービスが自動切替できる
profiles停止 docker compose --profile dev down 起動時と同じフラグを付けてプロファイルサービスを含めて停止する
任意依存(v2.22+) depends_on: required: false profilesを持つサービスへの依存を任意化し、未起動でもエラーにしない
extends(サービス継承) extends: {service: 名前} 別ファイルまたは同一ファイルのサービス定義を継承し差分だけ上書きする
include(ファイル分割) include: - path: ./infra/compose.yml(v2.20以降) compose.ymlを機能単位に分割してルートから取り込む。project_directoryで相対パス基点も指定できる
-f merge(動的合成) docker compose -f base.yml -f prod.yml up コマンドラインでファイルを動的にマージ。後から読んだ定義が上書き。環境差分の切替に使いやすい
YAMLアンカーは強力ですが、使いすぎると「参照を追うのが大変で結果的に読みにくくなる」という逆効果も起こります。共通化する設定はlogging・基本環境変数・リソース枠など、全サービスで確実に統一したい設定に絞るのが運用上のコツです。

サービス数が増えてcompose.ymlファイル自体を複数に分割したくなった場合は、Docker Compose v2.20で導入されたinclude機能が有効です。DB・Redisなどの共通インフラを別ファイルに切り出しつつ、docker compose up -d一発で全サービスを起動できる設計が実現できます。project_directoryオプションを使うと各サブcompose.yml内の相対パス基点を明示できるため、チームでの分担管理がしやすくなります。詳しくは次の関連記事で解説しています。

環境(開発・本番・テスト)でサービスの起動有無を切り替えたい場合はDocker Composeのprofiles機能が適しています。アンカーとprofilesは相互排他ではなく、同一のcompose.yml内で組み合わせて使えます。profiles機能の詳細な使い方は次のリンクの記事で解説しています。

次に読む記事

・docker compose includeでcompose.ymlをモジュール分割する方法|共通インフラを別ファイルに分離して再利用するCompose設計
・DockerのENV・ARG・env_fileを正しく使う設計|ビルド時と実行時の値の渡し方とsecrets
・Docker Composeのprofilesで開発・本番サービスを切り替える方法|環境別コンテナ起動の実践設計
・Linuxのポート確認コマンド完全ガイド|ssとlsofの使い分け

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、YAMLアンカー・Extension fields・profilesを含む実践的なDocker Compose設計手法をハンズオン形式でまとめた講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>

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

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

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

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

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

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

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

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

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。