
docker-compose -fの落とし穴
こんにちは、ラーニングサービスの平塚です。
突然ですが、みなさんは、開発作業中にこんなことを考えた経験はありませんか?
- 修正中のこの機能、本当に改善できているのかな? 修正前後の動作を見比べたい
- 普段とは違うデータセットでアプリケーションの見栄えを確認したい
今回は、こういった「ちょっとした確認」がしたくなった時に使える、Docker Composeの小ネタと注意事項を紹介します。
Docker Composeとは?
本題に入る前に、DockerとDocker Composeについて簡単に紹介します。
Dockerとは、アプリケーションの実行環境をコンテナという形式でまとめる仮想化技術を指します。それぞれのコンテナは独立して動作するので、1つのマシンで複数のコンテナを同時に動かしても互いに干渉しません。
コンテナの設定情報はDockerfileに記述します。
Docker Composeとは、複数のコンテナを協調して動かすためのDockerプラグインです。たとえば、Webサーバとデータベースサーバをそれぞれ1つのコンテナとして定義して、それらを同時に実行したい時はDocker Composeを利用します。
Docker Composeの設定情報はcompose.yamlファイルに記述します。
豆知識
Docker公式は、設定ファイルの拡張子にyamlを推奨しています。拡張子ymlでも動作に違いはありません。
https://docs.docker.com/compose/intro/compose-application-model/#the-compose-file
compose.yamlファイルを複数適用する
compose.yamlファイルは一度に複数指定できます。docker-compose
コマンドの-f
フラグを使うと、指定した順にcompose.yamlファイルが適用され、重複する設定項目は上書きされます。
https://docs.docker.jp/compose/extends.html#extends-multiple-compose-files
たとえば、PostgreSQLのデータベースサーバとして動作するコンテナを1つ作成するcompose.yamlファイルがあったとします。
services:
db:
container_name: postgres
image: postgres:17
ports:
- "5432:5432"
volumes:
- data:/var/lib/postgresql/data
restart: always
environment:
POSTGRES_DB: app
POSTGRES_USER: app-user
POSTGRES_PASSWORD: app-pass
volumes:
data:
ここに記載したポート番号だけを変更したコンテナを作成する時を考えましょう。まず差分を記述したcompose.override.yamlファイルを用意します。
# compose.override.yaml
services:
db:
ports:
- "5433:5432"
2つのyamlファイルを適用したコンテナを作成するために、docker-compose
コマンドの-f
フラグを使います。土台となるyamlファイルから順番に指定してください。
docker-compose -f compose.yaml -f compose.override.yaml up -d
なお、各yamlファイルの名前や配置場所は自由です。コマンドを実行する時は、カレントディレクトリからの相対パスを指定してください。
複数のcompose.yamlファイルを使う時の注意
docker-compose
コマンドの-f
フラグを使うと、最小限の差分を記述するだけで複数の環境を用意できます。先ほどのPostgreSQLコンテナを作るcompose.yamlから、2つの環境を用意する場合を考えてみましょう。
# ベースとなるcompose.yaml(sampleディレクトリに配置)
services:
db:
container_name: postgres
image: postgres:17
ports:
- "5432:5432"
volumes:
- data:/var/lib/postgresql/data
restart: always
environment:
POSTGRES_DB: app
POSTGRES_USER: app-user
POSTGRES_PASSWORD: app-pass
volumes:
data:
- 作成したい環境
- 環境A
- ポート5433で接続する
- 環境B
- ポート5434で接続する
- 環境A
まずは上書き用のcompose.yamlファイルを作成します。sampleディレクトリに、compose.a.yamlファイルとcompose.b.yamlファイルを配置しましょう。
# compose.a.yaml(環境A用の上書きcompose.yamlファイル)
services:
db:
ports:
- "5433:5432"
# compose.b.yaml(環境B用の上書きcompose.yamlファイル)
services:
db:
ports:
- "5434:5432"
環境A、環境Bの順番で起動します。
$ docker-compose -f compose.yaml -f compose.a.yaml up -d
[+] Running 3/3
✔ Network sample_default Created 0.1s
✔ Volume "sample_data" Created 0.0s
✔ Container postgres Started 0.3s
$ docker-compose -f compose.yaml -f compose.b.yaml up -d
[+] Running 1/1
✔ Container postgres Started 0.4s
無事に起動できたか、docker container ls
コマンドで確かめてみましょう。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c4669781fcb postgres:17 "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp, 0.0.0.0:5434->5432/tcp, [::]:5434->5432/tcp postgres
コンテナが1つしかありません。POSTSに5434->5432とありますので、環境Bのコンテナしか動いていないようです。
実は、compose.a.yamlファイルとcompose.b.yamlの内容には修正すべき点が3つあります。これから順番に説明します。
注意:この記事に従って実際に操作している場合は、以下のコマンドを実行して作成済のコンテナ等を削除してください。
docker container rm -f postgres
docker volume rm sample_data
docker network rm sample_default
修正1. プロジェクト名を指定する
Docker Composeを使って起動するコンテナ群のことをプロジェクトと呼びます。Docker Composeはプロジェクト単位で環境を分離します。複数のプロジェクトを同時に起動する時は、それぞれのプロジェクトに一意な名前をつけなければなりません。
プロジェクト名は以下の優先順位に従って決まります。
docker-compose
コマンドの-p
フラグ- 環境変数
COMPOSE_PROJECT_NAME
- compose.yamlファイルに記載されたトップレベルの
name
要素-f
フラグで複数のcompose.yamlファイルを指定した場合、最後に記載されたname
要素
- compose.yamlファイルを収めたディレクトリ
-f
フラグで複数のcompose.yamlファイルを指定した場合、最初に指定したcompose.yamlファイルを収めたディレクトリ名
- カレントディレクトリ名
https://docs.docker.com/compose/how-tos/project-name/#set-a-project-name
今回の例では、3つのcompose.yamlファイルすべてにプロジェクト名の記述がありません。したがって、環境Aを起動する時も、環境Bを起動する時も、プロジェクト名は「ベースとなるcompose.yamlファイルが配置されたディレクトリ名」つまり「sample
」です。2つの環境は同じプロジェクトだとみなされたため、環境Aの設定は後から起動した環境Bによって上書きされてしまったのです。
環境Aと環境Bが別のプロジェクトであると明示するため、上書き用compose.yamlファイルのトップレベルにname
要素を追加します。
# compose.a.yaml(プロジェクト名を追加)
name: env-a
services:
db:
ports:
- "5433:5432"
# compose.b.yaml(プロジェクト名を追加)
name: env-b
services:
db:
ports:
- "5434:5432"
ちなみに、この段階でdocker-compose
コマンドを実行すると、環境Bの起動時にエラーが発生します。
$ docker-compose -f compose.yaml -f compose.a.yaml up -d
[+] Running 3/3
✔ Network env-a_default Created 0.1s
✔ Volume "env-a_data" Created 0.0s
✔ Container postgres Started 0.3s
$ docker-compose -f compose.yaml -f compose.b.yaml up -d
[+] Running 3/3
✔ Network env-b_default Created 0.1s
✔ Volume "env-b_data" Created 0.0s
✘ Container postgres Error response from daemon: Conflict. 0.0s
Error response from daemon: Conflict. The container name "/postgres" is already in use by container "388db...". You have to remove (or rename) that container to be able to reuse that name.
どうやらコンテナ名が重複しているようです。
注意: docker-compose
コマンドを実行した場合は、念のため以下のコマンドを実行してください。
docker container rm -f postgres
docker volume rm env-a_data
docker volume rm env-b_data
docker network rm env-a_default
docker network rm env-b_default
修正2. コンテナ名を上書きする
コンテナ名postgres
は、ベースとなるcompose.yamlファイルのcontainer_name
属性で指定されたものです。この属性はdocker container run
コマンドの--name
オプションと同じ効果があり、プロジェクト名の影響を受けません。
Dockerは同じ名前を持つコンテナを複数起動できません。ベースとなるcompose.yamlファイルからcontainer_name
属性を削除するか、上書き用compose.yamlファイルで別のコンテナ名を付ける必要があります。今回は後者で進めましょう。
# compose.a.yaml(コンテナ名を追加)
name: env-a
services:
db:
container_name: a-db
ports:
- "5433:5432"
# compose.b.yaml(コンテナ名を追加)
name: env-b
services:
db:
container_name: b-db
ports:
- "5434:5432"
しかしまだエラーが発生します。
$ docker-compose -f compose.yaml -f compose.a.yaml up -d
[+] Running 3/3
✔ Network env-a_default Created 0.1s
✔ Volume "env-a_data" Created 0.0s
✔ Container a-db Started 0.3s
$ docker-compose -f compose.yaml -f compose.b.yaml up -d
[+] Running 2/3
✔ Network env-b_default Created 0.1s
✔ Volume "env-b_data" Created 0.0s
⠙ Container b-db Starting 0.1s
Error response from daemon: driver failed programming external connectivity on endpoint b-db (24c42...): Bind for 0.0.0.0:5432 failed: port is already allocated
今度はポート5432が重複しているようです。最後にここを直しましょう。
注意: docker-compose
コマンドを実行した場合は、念のため以下のコマンドを実行してください。
docker container rm -f a-db
docker container rm -f b-db
docker volume rm env-a_data
docker volume rm env-b_data
docker network rm env-a_default
docker network rm env-b_default
修正3. 属性を強制的に上書きする
基本的に、compose.yamlファイルの内容は、後から指定したcompose.yamlファイルの内容に上書きされます。ただし、ports
, expose
,
external_links
といった複数の値を持つ属性は、-f
フラグで指定したすべてのcompose.yamlファイルの内容を結合します。
https://docs.docker.com/reference/compose-file/merge
環境Aと環境Bは、それぞれの上書き用compose.yamlファイルに記載したポート(5433または5434)に加えて、ベースとなるcompose.yamlファイルに記載したポート(5432)も開放しようとします。しかし、環境Bの起動時には環境Aがすでにポート5432を使っているためエラーが発生します。
結合ではなく上書きする時は、属性名の隣に!override
キーワードを追加します。ただし、このキーワードはDocker Composeのバージョン2.24.4以上でしか使えません。
https://docs.docker.com/reference/compose-file/merge/#replace-value
# compose.a.yaml(!overrideキーワードを追加)
name: env-a
services:
db:
container_name: a-db
ports: !override
- "5433:5432"
# compose.b.yaml(!overrideキーワードを追加)
name: env-b
services:
db:
container_name: b-db
ports: !override
- "5434:5432"
ついにエラーがなくなりました。
$ docker-compose -f compose.yaml -f compose.a.yaml up -d
[+] Running 3/3
✔ Network env-a_default Created 0.1s
✔ Volume "env-a_data" Created 0.0s
✔ Container a-db Started 0.3s
$ docker-compose -f compose.yaml -f compose.b.yaml up -d
[+] Running 3/3
✔ Network env-b_default Created 0.1s
✔ Volume "env-b_data" Created 0.0s
✔ Container b-db Started 0.3s
docker container ls
コマンドで確かめると、2つのコンテナが同時に実行されていることが分かります。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3465ef5d837 postgres:17 "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5434->5432/tcp, [::]:5434->5432/tcp b-db
e6011ef117bd postgres:17 "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5433->5432/tcp, [::]:5433->5432/tcp a-db
後片付け
以下のコマンドで作成したコンテナ等を削除してください。
docker container rm -f a-db
docker container rm -f b-db
docker volume rm env-a_data
docker volume rm env-b_data
docker network rm env-a_default
docker network rm env-b_default
まとめ
docker-compose
コマンドは、-f
フラグを使うと複数のcompose.yamlファイルを指定できます。ただし、複数の環境を同時に実行したい時は、以下の3つに注意しましょう。
- プロジェクト名を明示する
container_name
属性は必ず上書きする- 属性がマージされる時の挙動を意識する
- 強制的に上書きしたい時は
!override
キーワードを使う
- 強制的に上書きしたい時は
研修コース紹介
カサレアルでは、Dockerをはじめとしたインフラ系の知識を学べるコースを開催しています。詳細や開催日程は以下のリンクを御覧ください。
https://www.casareal.co.jp/ls/service/openseminar/search/cloudinfra
また、カサレアルでは、座学形式の講義のほかにも、一社様向けに課題解決型(Problem Based Learning, PBL)の研修サービスも提供しています。
講義でインプットした知識を、実際の開発演習を通じてアウトプットすることで、理解の深化と知識の定着を図ることができます。
さらにチームごとに開発したい機能を設計し、開発からテストまで一貫して取り組むことで、プロダクト開発にまつわる知識の定着や深堀り、また困った時の調べ方、助けの求め方も学べるとご好評いただいております。
今回の記事は、演習をサポートする立場として「受講者の手元にあるソースコードは極力変更せずに、全チームの成果物を同時に起動したい」というニーズを満たすべく試行錯誤した結果をまとめました。
チーム開発演習の規模や採用技術は柔軟に対応いたします。詳細についてはお気軽にお問い合わせください。