🐋

Dockerでバインドマウントするのやめません?もうCompose Watchでいいでしょ?Docker composeの新機能を使い倒そう

に公開
6

はじめに

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
docker/app.Dockerfile
FROM node:20.18.1-alpine3.21

WORKDIR /app

COPY package*.json ./
RUN npm install
COPY . .

CMD ["npm", "run", "dev"]

よくある設定とその問題

ホストとコンテナ間でソースコードを同期するため、以下のようにバインドマウントを設定することが多いです。

compose.yml
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 を名前付きボリュームで分離する方法が一般的でした。

compose.yml
services:
  app:
    volumes:
      - .:/app
      - app-node_modules:/app/node_modules  # node_modulesを分離

volumes:
  app-node_modules:

ただし、この方法には面倒な点があります。


Compose Watch で解決する

Compose Watch を使えば、バインドマウントなしでファイル同期と自動リビルドを実現できます。

compose.yml
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でバインドマウントするのやめな〜」にしようか迷ったことは秘密です🤫

tomyntomyn

とても参考になりました。

こちらの方法で開発する場合サーバサイドのエラーはどうやって確認されるのでしょうか?
docker compose watchだと見れなかったのですが、何かコマンドオプションが必要なのでしょうか?

yumetodoyumetodo

しかしコンテナ内がLinuxの場合、node_modules/配下に作成されたバイナリは互換性がなく動かない可能性もあります。installするnpmパッケージによっては動作しないということも起きえるのでnode_modulesをバインドマウントはできないということになります。

結局この部分はcompose watchするとcontainerがnode_moduesを持つので共有されなくなり解決できる、ということで合っていますか?