DockerCompose で複数ファイルを指定するとどうなる？

知らんのか（枯れたネタ）

ということで、船井総研デジタルの Advent Calendar、2023/12/5 は @fsd-sayhorn がお届け！  
結果だけ知りたい人は結論だけ読めば OK！
そう言えば、何気に Advent Calendar は初めて参加したかも。

# これは何？

## 概要

みんな大好き Docker（誇張、あると思います）。  
Docker では複数コンテナをまとめて扱える [Compose という機能が提供](https://docs.docker.com/compose/)されています。  
Compose では制御するコンテナ群やその設定を YAML 形式のファイルで管理します。  
たとえば、（Compose ファイルの中身は一旦置いておくとして）下記のような使い方をします。

```bash
docker compose -f my-compose.yml up
[+] Running 2/2
 ✔ ubuntu 1 layers [⣿]      0B/0B      Pulled                                                                6.8s
   ✔ f93f952dad40 Pull complete                                                                              3.9s
[+] Running 2/2
 ✔ Network qiita-20231205-dockercompose_default     Create...                                                0.1s
 ✔ Container qiita-20231205-dockercompose-ubuntu-1  Cre...                                                   0.2s
Attaching to qiita-20231205-dockercompose-ubuntu-1
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_ID=Ubuntu
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_RELEASE=23.04
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_CODENAME=lunar
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_DESCRIPTION="Ubuntu 23.04"
qiita-20231205-dockercompose-ubuntu-1 exited with code 0
```

<details><summary>my-compose.yml の中身が知りたい人はこちら</summary>

```yml:my-compose.yml
services:
  ubuntu:
    image: ubuntu:23.04
    command: /bin/bash -c 'cat /etc/lsb-release'
```

`cat /etc/lsb-release` はディストリビューションの情報を確認する手段の一つです。  
他にも `lsb_release` コマンドや　`cat /etc/os-release` を実行することでも確認できます。  
ちなみに Linux カーネルのバージョンは `uname -a` などとすると確認できます。

```bash:ここで紹介した各コマンドの出力例
# WSL2 で動作する Ubuntu 22.04 で実行した結果
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:        22.04
Codename:       jammy

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

$ uname -a
Linux PC-M066 5.15.133.1-microsoft-standard-WSL2 #1 SMP Thu Oct 5 21:02:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
```

</details>

この使い方では、`-f` オプションによって任意のファイル名を指定することができます。  
今回は、このファイルを指定する際に複数のファイルを指定するとどうなるのか、ということがテーマです。

:::note warn
Compose には V1 と V2 があり、2023 年 7 月以降は V1 ではアップデートを受信しなくなりました。
現在では Compose V2 への移行が推奨されていますので、[Migrate to Compose V2](https://docs.docker.com/compose/migrate/) を参考に移行しましょう。
コマンドフォーマットやデフォルトファイル名の変更なども含まれていますが、旧コマンド体系でも動作するなど互換性・利便性は考慮されているようです。
:::

## 結論

複数の compose.yml ファイルが指定された場合、指定したファイルがマージされたかのように動作します。  
たとえば、下記のようなファイルを想定した場合、ubuntu:22.04 イメージは pull されず、ubuntu:23.04 のイメージだけが pull されます。  
（my-compose-second.yml で image が指定されているので、my-compose-first.yml で ubuntu:22.04 と指定されていた image が ubuntu:23.04 で上書きされる）

- 実行するコマンド（下記のいずれか）
  - `docker compose -f my-compose-first.yml -f my-compose-second.yml up`
  - `COMPOSE_FILE=my-compose-first.yml:my-compose-second.yml docker compose up`

    :::note info
    `-f` オプションや `COMPOSE_FILE` 環境変数の指定順序が重要です。
    前（左）に指定されたものから適用され、後（右）に書いたもので上書きされます。
    :::
- 使用するファイル
  - ```yaml:my-compose-first.yml
    services:
      ubuntu:
        image: ubuntu:22.04
        command: /bin/bash -c 'cat /etc/lsb-release'
    ```
  - ```yml:my-compose-second.yml
    services:
      ubuntu:
        image: ubuntu:23.04
    ```

# 解説・考察

## 前提(docker 環境)

```bash
$ docker version
Client: Docker Engine - Community
 Version:           24.0.7
 API version:       1.43
 Go version:        go1.20.10
 Git commit:        afdd53b
 Built:             Thu Oct 26 09:07:41 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.7
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.10
  Git commit:       311b9ff
  Built:            Thu Oct 26 09:07:41 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.25
  GitCommit:        d8f198a4ed8892c764191ef7b3b06d8a2eeb5c7f
 runc:
  Version:          1.1.10
  GitCommit:        v1.1.10-0-g18a0cb0
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

## Compose ファイルを指定する機能について

[Docker-docs-ja 24.0/docker compose](https://docs.docker.jp/engine/reference/commandline/compose.html) によると、下記のような方法で Compose ファイルの指定が可能であり、また複数指定することができると書いてあります。

1. [`-f` オプション](https://docs.docker.jp/engine/reference/commandline/compose.html#f-compose)
   > `-f` フラグを使い、Compose 設定ファイルの場所を指定します。
1. [`COMPOSE_FILE` 環境変数](https://docs.docker.jp/engine/reference/commandline/compose.html#set-up-environment-variables)
   > `-f` 、 `-p` 、 `--profile` フラグを含む、様々な docker compose オプションを環境変数で指定できます。  
   > `COMPOSE_FILE` 環境変数の設定は、 `-f` フラグを渡すのと同じです。 `COMPOSE_PROJECT_NAME` 環境変数は、 `-p` フラグを渡すのと同じです。さらに `COMPOSE_PROFILES` 環境変数は、 `--profile` フラグを渡すのと同じです。  
   > フラグが明示的に指定される場合は、関連する環境変数は無視されます。
1. [複数の Compose ファイルを指定](https://docs.docker.jp/engine/reference/commandline/compose.html#compose)
   > 複数の `-f` 設定ファイルを追加指定できます。複数のファイルを指定すると、 Compose が 1 つの設定の中にそれらを統合します。指定したファイル順の設定で、 Compose が構築します。後続のファイルは、事前の処理を上書き・追加します。

### 本機能を活用できそうなシチュエーション

本機能の特性（後から指定したファイルの内容で上書き・追加する）を考慮すると、たとえば次のような活用シチュエーションが考えられそうです。

1. 環境別にコンテナを作り分ける  
   開発環境にはパケット解析ツールをインストールしたいが本番環境にはインストールしたくないなど、環境別で微妙に異なる差分が発生することはよくあると思います。  
   こうした場合、ベースとなる Compose ファイルを作成し、差分だけを記述した Compose ファイルを環境ごとに用意して適用することで実現できそうです。  
   ただし、この場合は共通のベースコンテナイメージを作成し、それを元に差分をビルドする方が適切なケースもあり得るので、検討が必要かもしれません。  
   他にも docker ホストの CPU アーキテクチャによって異なるコンテナを作りたいという場合もあるかもしれませんね。
1. 他の人が作った Compose ファイルに影響を与えることなく自分専用のカスタマイズを追加する  
   誰かが作ったある環境に対して追加の設定やツールのインストール等を行いたいというニーズもまたよくあると思います。  
   こうした状況では、本機能を使わない場合には直接 Compose ファイルを書き換えたり、元の Compose ファイルのコピーを作成してそれを編集するなどすることになります。  
   しかし、そのようにしてしまうと二重管理になったり、問題が発生した時に切り分けるのが難しくなったりすることが考えられます。  
   本機能を使えば、元の Compose ファイルは編集しないのでこうした問題は発生しません。

## 実験

[結論](#結論)で示したファイルを用いる実験環境を構築し、実際に実行してみてどのような状況になるか観察してみます。

### 事前準備

下記のような構成となるようにワークスペース（ディレクトリ）を用意します。  
なお、今回は `dockerd` には特殊な指定はしていませんので、一般的な linux/amd64 環境を想定しています（筆者は Windows Subsystem for Linux 環境で実施しました）。

#### バージョン情報

|            項目             | バージョン  |
| :-------------------------: | :---------: |
| Windows Subsystem for Linux |   2.0.9.0   |
|           Ubuntu            | 22.04.3 LTS |
|       docker(client)        |   24.0.7    |
|       docker(server)        |   24.0.7    |
|       Docker Compose        |   2.21.0    |

#### ディレクトリ構成

```bash:ディレクトリ構成
$ pwd
/home/username/work/qiita-20231205-dockercompose
$ tree
.
├── my-compose-first.yml
└── my-compose-second.yml

0 directories, 2 files
```
<details><summary>(補足) tree コマンドについて</summary>

`tree` コマンドがインストールされていない人は `apt` などを使ってインストールすると同じことができます。
※`tree` を使わなければいけない理由はないのでわざわざインストールしなくてもいいとは思いますが、お好みでどうぞ。
</details>

### 実験開始前のコンテナリソース状況（関係あるものだけ抜粋）

```bash
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ docker iamges -a
REPOSITORY  TAG                    IMAGE ID       CREATED        SIZE
node        20.7.0-bullseye-slim   a1c6dc3eddfa   2 months ago   253MB
```

<details><summary>(補足) node:20.7.0-bullseye-slim イメージについて</summary>

:::note info
`node:20.7.0-bullseye-slim` イメージがありますが、後の工程で必要なイメージだけ抽出するためにこのイメージの作成日をフィルタ条件として使用しています。
それ以外の用途はありませんので、このイメージは本実験とは本質的に無関係です。
フィルタの例)
`docker images --filter "since=node:20.7.0-bullseye-slim"`
:::

</details>

### 実験結果

下記に示すとおり、`-f` オプションの場合も `COMPOSE_FILE` 環境変数の場合も、Compose ファイルの指定順序に従って各ファイルの内容がマージされて実行されたような結果となりました。  
仕様通り実装されていることが確認できたと言えるでしょう。

```bash:first->secondの順で-fオプションを使った場合
$ docker compose -f my-compose-first.yml -f my-compose-second.yml up
[+] Running 2/2
 ✔ ubuntu 1 layers [⣿]      0B/0B      Pulled                                                         7.1s
   ✔ 6360b3717211 Pull complete                                2.7s
[+] Running 1/1
 ✔ Container qiita-20231205-dockercompose-ubuntu-1  Created                                                        0.1s
Attaching to qiita-20231205-dockercompose-ubuntu-1
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_ID=Ubuntu
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_RELEASE=23.04
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_CODENAME=lunar
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_DESCRIPTION="Ubuntu 23.04"
qiita-20231205-dockercompose-ubuntu-1 exited with code 0
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                 CREATED         STATUS                   PORTS  NAMES
aefa7e06b022   ubuntu:23.04 "/bin/bash -c 'cat /…"   7 seconds ago   Exited (0) 5 seconds ago        qiita-20231205-dockercompose-ubuntu-1
$ docker images -a --filter "since=node:20.7.0-bullseye-silm"
REPOSITORY  TAG                    IMAGE ID       CREATED        SIZE
ubuntu      23.04                  f4cdeba72b99   4 days ago     70.3MB
```

```bash:second->firstの順で-fオプションを使った場合
$ docker compose -f my-compose-second.yml -f my-compose-first.yml up
[+] Running 2/2
 ✔ ubuntu 1 layers [⣿]      0B/0B      Pulled                  7.0s
   ✔ 5e8117c0bd28 Pull complete                                2.8s
[+] Running 1/1
 ✔ Container qiita-20231205-dockercompose-ubuntu-1  Created    0.1s
Attaching to qiita-20231205-dockercompose-ubuntu-1
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_ID=Ubuntu
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_RELEASE=22.04
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_CODENAME=jammy
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"
qiita-20231205-dockercompose-ubuntu-1 exited with code 0
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                 CREATED         STATUS                   PORTS  NAMES
adfcfbd8db03   ubuntu:22.04  "/bin/bash -c 'cat /…"  10 seconds ago  Exited (0) 10 seconds ago       qiita-20231205-dockercompose-ubuntu-1
$ docker images -a --filter "since=node:20.7.0-bullseye-silm"
REPOSITORY  TAG                    IMAGE ID       CREATED        SIZE
ubuntu      22.04                  b6548eacb063   39 hours ago   77.8MB
```

```bash:first->secondの順でCOMPOSE_FILE環境変数を使った場合
$ COMPOSE_FILE=my-compose-first.yml:my-compose-second.yml docker compose up
[+] Running 2/2
 ✔ ubuntu 1 layers [⣿]      0B/0B      Pulled                 6.7s
   ✔ 6360b3717211 Pull complete                               2.5s
[+] Running 1/1
 ✔ Container qiita-20231205-dockercompose-ubuntu-1  Created   0.1s
Attaching to qiita-20231205-dockercompose-ubuntu-1
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_ID=Ubuntu
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_RELEASE=23.04
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_CODENAME=lunar
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_DESCRIPTION="Ubuntu 23.04"
qiita-20231205-dockercompose-ubuntu-1 exited with code 0
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                 CREATED         STATUS                   PORTS  NAMES
104e117f1ca5   ubuntu:23.04  "/bin/bash -c 'cat /…"  7 seconds ago   Exited (0) 6 seconds ago        qiita-20231205-dockercompose-ubuntu-1
$ docker images -a --filter "since=node:20.7.0-bullseye-silm"
REPOSITORY  TAG                    IMAGE ID       CREATED        SIZE
ubuntu      23.04                  f4cdeba72b99   4 days ago     70.3MB
```

```bash:second->firstの順でCOMPOSE_FILE環境変数を使った場合
$ COMPOSE_FILE=my-compose-second.yml:my-compose-first.yml docker compose up
[+] Running 2/2
 ✔ ubuntu 1 layers [⣿]      0B/0B      Pulled                 7.0s
   ✔ 5e8117c0bd28 Pull complete                                2.7s
[+] Running 1/1
 ✔ Container qiita-20231205-dockercompose-ubuntu-1  Created    0.1s
Attaching to qiita-20231205-dockercompose-ubuntu-1
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_ID=Ubuntu
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_RELEASE=22.04
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_CODENAME=jammy
qiita-20231205-dockercompose-ubuntu-1  | DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"
qiita-20231205-dockercompose-ubuntu-1 exited with code 0
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                 CREATED         STATUS                   PORTS  NAMES
3d2b7aac45bf   ubuntu:22.04  "/bin/bash -c 'cat /…"  7 seconds ago   Exited (0) 6 seconds ago        qiita-20231205-dockercompose-ubuntu-1
$ docker images -a --filter "since=node:20.7.0-bullseye-silm"
REPOSITORY  TAG                    IMAGE ID       CREATED        SIZE
ubuntu      22.04                  b6548eacb063   39 hours ago   77.8MB
```

# まとめ

今回は Compose ファイルを複数指定できる機能について検証してみました。  
Docker は結構実務でも活用していますが、Docker の仕様はプロダクトの進化に伴って増加していますのでまだまだ知らない機能がたくさんありそうです。  
その内、`--isolation` の動作なんかも深掘りしてみたいなと思っています。

では、年末・年度末進行の方々など、みなさま日々お忙しいとは思いますが、良きエンジニアライフをお過ごしください。