ローカルコンテナのホットリロードにDocker Compose Watchはいかが？

## はじめに

:::note info

この記事はローカルの開発環境でコンテナを用いている人に向けた開発効率化を目的としたものです。

:::

dockerのようなコンテナ技術を用いてローカル開発を行うことにはさまざまなメリットがあります。例えば
- 開発環境の共通化
- 開発環境構築の難易度軽減
- ローカルマシンをクリーンに保てる


などです。
しかし、同様にいくつかの課題も存在し、その中の一つとして工夫しないとホットリロードができない、というものがあります😭

今回はその「工夫しないとホットリロードができない」を簡単に解決してくれる機能である`Docker Compose Watch`を紹介したいと思います！

## 従来のコンテナを用いたホットリロードな開発環境

### インタープリタ型言語

JavaScriptなどのインタープリタ型言語では再ビルドの必要がないため、次のようにソースコードが格納されたディレクトリを丸ごとコンテナにマウントするケースが多かったかと思います。
```yaml:conpose.yaml
version: "3.8"

services:
  my-app:
    container_name: my-app
    build:
      context: .
    tty: true
    volumes:
      - ./src:/app/src:cached
    ports:
      - 3000:3000
```
しかし、`node_modules`のようなインストールしたライブラリを扱うディレクトリは`linter`などの開発のみに使うようなライブラリも含んでおり、そのままマウントされては困る場面が存在します。そのため、`package.json`の更新が入った場合はコンテナをビルドし直す、といった方針を取られていた方も多いのではないでしょうか :thinking: 

### コンパイル型言語(or JVM系言語)

Goなどのコンパイル言語は基本的にはコンパイルし直さないとソースコードの変更がアプリに反映されないため、ソースコードを変更するたびに再ビルド、あるいはライブラリなどでホットリロードを可能にした上でコンテナ内部に入って開発する、といったことが多かったかと思います。
しかし、ソースコードの更新のたびにコンテナを手動でビルドし直すのは手間です。また、ホットリロードを有効化してコンテナ内部で開発を行うのにはコンテナ内部での開発が快適に行えるような専用の環境やIDE側でのセットアップが必要であり、すでに環境が整っているようなケースを除いては準備が手間ですし、環境構築の難易度も高くなってしまいがちです :cry: 

そこで`Docker Compose Watch`の出番です。

## `Docker Compose Watch`とは？

`Docker Compose Watch`とは、`Docker Compose`に内蔵されている機能の一つで**ローカルファイルの変更を検知し、コンテナ内部のファイルを置き換えたり、コンテナを再ビルドしてくれる機能**です。
`compose.yaml` or `docker-compose.yaml`に規定の項目を書いた上で

```shell
docker compose watch
```
のコマンドを実行することで使用できます！
`Docker Compose Watch`には`sync`と`rebuild`の二つの機能があり、それぞれ

- syncは追跡中のローカルのファイルに変更が生じたら、自動で実行中のコンテナ内のファイルを設定通りに置き換える
- rebuildは追跡中のローカルのファイルに変更が生じたら、自動でコンテナを再ビルドする

といった機能となっています！


下記で例を見てみましょう！

## `Docker Compose Watch`を用いたホットリロードな開発環境

### インタープリタ型言語

例えばpackage.jsonをプロジェクトルートディレクトリに含み、`./src`以下にあるソースコードをコンテナ内の`/app/src`に配置するようなNodeJSのアプリを扱う場合に次のように設定したとします。
```diff_yaml:compose.yaml
version: "3.8"

services:
  my-app:
    container_name: my-app
    build:
      context: .
+   develop:
+     watch:
+       - action: sync
+         path: ./src
+         target: /app/src
+       - action: rebuild
+         path: ./package.json  
    tty: true
    volumes:
      - ./src:/app/src:cached
    ports:
      - 3000:3000
```

こうすることで、下記のような動作が発生し、外部ライブラリに頼らずともお手軽にホットリロードが実現できます！
- `./src`以下が更新された時、コンテナ内の`/app/src`以下のファイル・ディレクトリが手元の`./src`以下のファイル・ディレクトリに置き換わる
- `package.json`が更新された時、自動でコンテナが再ビルドされる

### コンパイル型言語

例えばライブラリを用いずにGolangでホットリロードを実現したい場合に次のように設定したとします。

```diff_yaml:compose.yaml
version: "3.8"

services:
  my-app:
    container_name: my-app
    build:
      context: .
+   develop:
+     watch:
+       - action: rebuild
+         path: .
    tty: true
    ports:
      - 3000:3000
```
このようにすると、ソースコードだけでなく`go.mod`などのファイルが更新された際も、自動でコンテナが再ビルドされ、ホットリロードが実現できます！

### え、でも不要なファイルの変更まで検知されるのはちょっと。。。 → ignoreオプションもあります！ 

タイトルの通り、ignoreオプションで検知に検知に使用したくないファイルを除外することもできます！
例えば、先ほどのコンパイル型言語の例で、プロジェクトルートにある`README.md`の変更を無視したい場合は次のようにします。

```diff_yaml:compose.yaml
version: "3.8"

services:
  my-app:
    container_name: my-app
    build:
      context: .
+   develop:
+     watch:
+       - action: rebuild
+         path: .
+         ignore:
+           - README.md
    tty: true
    ports:
      - 3000:3000
```

## まとめ

日々ライブラリの管理・更新に悩まされている我々エンジニアにとって`Docker Compose Watch`のような、インフラレイヤーでホットリロードを可能にする技術が登場したことは大変嬉しいことですね :smile: 
皆様もぜひ、Dockerを用いた快適な開発ライフを送ってください :tada:

## 参考文献

https://docs.docker.com/compose/file-watch/#configuration

https://developer.hatenastaff.com/entry/2023/10/13/120000

https://qiita.com/hiroki-yod/items/9b065f1b66ebb727f177