docker-compose.ymlをDRYに書くテクニック2選

最近仕事で `docker-compose.yml` をメンテする機会が多く、その際に重宝したDRYに書くテクニック2つです。

## Extension fields
[Extension fields(公式ドキュメント)](https://docs.docker.com/compose/compose-file/#extension-fields)

compose fileのバージョン`3.4`以降から、Extension fieldsという機能が利用できるようになりました。
これは、YAMLのaliasとanchorの機能を`docker-compose.yml`中でも利用できるようにしたものです。

### 使いどころ

- 同じ環境変数を複数のコンテナに設定しなければならない
- 複数コンテナで同じパスでマウントするvolumeがある

### 使用例
サンプルとして、大量の同じ`environment`を各コンテナで指定しなければならない例を見てみましょう。

適用前は次に示すように愚直に記述しています。

```yaml:docker-compose.yml
---
version: '3.7'

services:
  nginx:
    image: nginx:alpine
    ports:
      - 80:80
    environment:
      ENV: development
      USER: user
      HOST: example.com
      HOGE: hoge
      FUGA: fuga
      FOO: foo
      BAR: bar

  rails:
    image: ruby:alpine
    environment:
      ENV: development
      USER: user
      HOST: example.com
      HOGE: hoge
      FUGA: fuga
      FOO: foo
      BAR: bar
```

これをExtension fieldsを使用して書くと次のように書けます。

```yaml
---
version: '3.7'

x-environment: &environment
  ENV: development
  USER: user
  HOST: example.com
  HOGE: hoge
  FUGA: fuga
  FOO: foo
  BAR: bar

services:
  nginx:
    image: nginx:alpine
    ports:
      - 80:80
    environment: *environment

  rails:
    image: ruby:alpine
    environment: *environment
```

`x-environment`に`&environment`でaliasを設定し、`*environment`で展開して利用しています。
このように書くことで、環境変数を変更する際、すべてのコンテナの記述を変更してまわらなくてもよくなります。

### 注意点

- `version`には`3.4`以降を指定する必要があります
- トップレベルに記載する場合はキーを`x-`で始める必要があります

YAMLのalias、anchorは他にも記述方法がいくつかあります。
以下のサイトが参考になるかと思います。
https://tech-1natsu.hatenablog.com/entry/2018/12/16/004215

## Multiple Compose files
[Multiple Compose files(公式ドキュメント)](https://docs.docker.com/compose/extends/)

Multiple Compose filesはその名前の通り、複数のcomposeファイルを使う方法です。

通常、`docker-compose`コマンドを利用すると、カレントディレクトリ（存在しない場合は上位のディレクトリ）の`docker-compose.yml`が利用されます。
このファイルは、`-f`オプションか`COMPOSE_FILE`環境変数で指定を変更することができます。

`docker-compose.develop.yml`を使って、`docker-compose up -d`を行う場合は次のようになります。

```sh
# -fオプションの場合
docker-compose -f docker-compose.develop.yml up -d

# 環境変数の場青
COMPOSE_FILE=docker-compose.develop.yml docker-compose up -d
# or
export COMPOSE_FILE=docker-compose.develop.yml
docker-compose up -d
```

この`-f`オプションか環境変数`COMPOSE_FILE`に複数のdocker-compose用YAMLファイルを指定することで、マージされた内容が利用できます。これがMultiple Compose filesです。

### 使いどころ

- 開発時は追加で起動するコンテナがある
- 連携サービス用コンテナの有無や、環境変数などを自由に切り替えられるようにしたい


### 使用例
以下のような例を想定します。

- nginxとrailsの2つのコンテナの環境
- 開発時はnginx側で8080とホストの8080をバインドしたい
- railsには開発時のみ、`HOGE=hoge`という環境変数を設定したい

普通にやろうとすると、docker-compose.ymlを2種作り、それぞれに似た内容を記述してしまい、DRYになりません。
Multiple Compose filesの機能を使いDRYに書くと、下記のように記述できます。

```yaml:docker-compose.yml
---
version: '3.7'

services:
  nginx:
    image: nginx:alpine
    ports:
      - 80:80

  rails:
    image: ruby:alpine
```

```yaml:development.yml
---
version: '3.7'

services:
  nginx:
    ports:
      - 8080:8080

  rails:
    environment:
      HOGE: hoge
```

`development.yml`には、開発時のみに利用する設定を記述しています。

利用時は以下のように使います。

```sh
# 非開発時
docker-compose up -d

# 開発時
COMPOSE_FILE=docker-compose.yml:development.yml docker-compose up -d
```

このように設定することで、開発時には`development.yml`の内容とマージされた設定でコンテナが起動できます。

`docker-compose config`コマンドで最終的なYAMLを表示できるので、`development.yml`を追加した場合の設定を見てみましょう。

```sh
COMPOSE_FILE=docker-compose.yml:development.yml dc config
# 以下が出力される
services:
  nginx:
    image: nginx:alpine
    ports:
    - published: 80
      target: 80
    - published: 8080
      target: 8080
  rails:
    environment:
      HOGE: hoge
    image: ruby:alpine
version: '3.7'
```

想定どおりになっていますね。

### 注意点
マージの挙動に関しては、下記のようになります。（詳細は公式ドキュメントを参照してください）

- キーが無い場合：追加
- キーがある場合：後で指定したファイルの内容で上書き
- リストの場合：リストの要素を追加

`docker-compose`コマンドを利用するたびに設定するのは面倒なので、[direnv](https://github.com/direnv/direnv)等を使い、自動で`COMPOSE_FILE`環境変数が設定されるようにすることをおすすめします。