Dockerでバインドマウントするのやめません?もうCompose Watchでいいでしょ?Docker composeの新機能を使い倒そう
はじめに
Docker × React/Vue などのフロントエンド開発で、node_modules のバインドマウントに悩まされたことはありませんか?
Compose Watch(Docker Compose 2.22.0〜)を使えば、この問題をスマートに解決できます。
バインドマウントの問題点
サンプル構成
以下のようなReactプロジェクトを例に説明します。
.
├── docker/
│ └── app.Dockerfile
├── node_modules/
├── src/
│ └── App.tsx
├── .dockerignore
├── compose.yml
├── package.json
└── package-lock.json
FROM node:20.18.1-alpine3.21
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
よくある設定とその問題
ホストとコンテナ間でソースコードを同期するため、以下のようにバインドマウントを設定することが多いです。
services:
app:
build:
context: .
dockerfile: docker/app.Dockerfile
ports:
- "3000:3000"
volumes:
- .:/app # ホストの . をコンテナの /app にマウント
しかし、この設定には重大な問題があります。
なぜ node_modules のバインドマウントは危険なのか?
ホスト側で npm install すると、ホストのOS向けにビルドされたバイナリが node_modules/ に作成されます。
例えば、bcrypt のようなネイティブモジュールを含むパッケージは、C++でコンパイルされたバイナリを使用します。
| 環境 | 生成されるバイナリ |
|---|---|
| Windows | Windows用バイナリ |
| macOS | macOS用バイナリ |
| Linux (コンテナ) | Linux用バイナリ |
ホスト(Windows/macOS)で生成した node_modules をそのままLinuxコンテナにマウントすると、バイナリの互換性がなく動作しない可能性があります。
従来の解決策:名前付きボリューム
従来は、node_modules を名前付きボリュームで分離する方法が一般的でした。
services:
app:
volumes:
- .:/app
- app-node_modules:/app/node_modules # node_modulesを分離
volumes:
app-node_modules:
ただし、この方法には面倒な点があります。
Compose Watch で解決する
Compose Watch を使えば、バインドマウントなしでファイル同期と自動リビルドを実現できます。
services:
app:
build:
context: .
dockerfile: docker/app.Dockerfile
ports:
- "3000:3000"
develop:
watch:
# package.json が変更されたら自動で再ビルド
- action: rebuild
path: package.json
# ソースコードの変更をコンテナに同期
- action: sync
path: .
target: /app
ignore:
- node_modules/
- .git/
- dist/
- coverage/
- .next/
- storybook-static/
- tmp/
起動方法
# バックグラウンドで起動(ログなし)
docker compose watch
# ログを確認しながら起動(おすすめ)
docker compose up --build --watch
Watch アクションの詳細
rebuild アクション
指定したファイルが変更されると、Dockerイメージを自動で再ビルドします。
- action: rebuild
path: package.json
ロックファイルを併用する場合の例(npm / pnpm / yarn):
- action: rebuild
path: package-lock.json
- action: rebuild
path: pnpm-lock.yaml
- action: rebuild
path: yarn.lock
例えば、ホストで新しいパッケージをインストールすると:
npm install axios
自動的にリビルドが実行されます。
Rebuilding service "app" after changes were detected...
[+] Building 1.9s (12/12) FINISHED
...
service "app" successfully built
sync アクション
指定したパスのファイル変更を、コンテナ内に自動同期します。
- action: sync
path: . # 監視するホスト側のパス
target: /app # 同期先のコンテナ内パス
ignore:
- node_modules/ # 同期から除外
ソースコードを変更すると:
Syncing service "app" after 1 changes were detected
まとめ
| 方法 | メリット | デメリット |
|---|---|---|
| バインドマウント | シンプル | node_modules の互換性問題 |
| 名前付きボリューム | 互換性問題を回避 | ボリューム管理が面倒 |
| Compose Watch | 自動同期・自動リビルド | Docker Compose 2.22.0以降が必要 |
Compose Watch を使えば、node_modules の問題を意識せずに開発できます。ぜひ試してみてください!
間違っている点やご質問があれば、コメントでお知らせください🙏
Discussion
初Zenn投稿失礼します。最初タイトルをダレノガレ明美風に「ねえ、Dockerでバインドマウントするのやめな〜」にしようか迷ったことは秘密です🤫
とても参考になりました。
こちらの方法で開発する場合サーバサイドのエラーはどうやって確認されるのでしょうか?
docker compose watchだと見れなかったのですが、何かコマンドオプションが必要なのでしょうか?
読んでいただいてありがとうございます!
サーバーサイドの言語では試せていないのですが
docker compose up --build --watchでログが出力されるのでこちらを試していただけると🙏参考: https://github.com/docker/compose/issues/11089#issuecomment-2079515066
ありがとうございます!
結局この部分はcompose watchするとcontainerがnode_moduesを持つので共有されなくなり解決できる、ということで合っていますか?
ありがとうございます!あってます!