できない.dev

docker compose のボリュームマウントで node_modules が消える

ホスト側ディレクトリを `/app` などに bind mount すると、コンテナ内でビルド時に作成された `node_modules` がホストの空ディレクトリに上書きされ消失する。
アプリ配下に named volume を別途マウントして退避させるのが定石。

#compose#volume#bind-mount#node-modules

公開:

要約

docker compose でホスト側のソースディレクトリを ./:/app のように bind mount すると、ビルド時に作った /app/node_modules がホスト側の空ディレクトリで覆われて見えなくなる。/app/node_modules 自体を named volume として別マウントし、bind mount より優先させる のが定石。

よくある原因

  1. bind mount の上書き: ホストに node_modules が無い状態で ./:/app をマウントすると、/app/node_modules が空ディレクトリで覆われる
  2. イメージ層で npm install 済み: Dockerfile で RUN npm ci していても、ランタイムの bind mount に上書きされて意味を失う
  3. OS / arch 非互換: ホスト(macOS arm64)の node_modules を Linux コンテナで使うと esbuild / sharp などのネイティブバイナリが動かない
  4. 匿名ボリュームの揮発: volumes: ['/app/node_modules'] だけ書くと匿名ボリュームになり、docker compose down -v で消える

解決策

1. named volume を node_modules に重ねる

services:
  app:
    build: .
    volumes:
      - ./:/app
      - node_modules:/app/node_modules
 
volumes:
  node_modules:

具体的なパスへのマウントが bind mount より優先されるため、ホスト側の空 node_modules で上書きされない。
仕様は services リファレンス に記載されている。

2. Dockerfile で先に依存を解決する

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]

package*.json を先にコピーするとレイヤキャッシュが効き、依存追加のたびに全コピーが走らない。

3. ネイティブモジュール対策

ホストとコンテナで OS / arch が異なる場合は node_modules を named volume に隔離する。
手動で再構築するなら:

docker compose exec app rm -rf node_modules
docker compose exec app npm ci

4. down -v の挙動を理解する

docker compose down は named volume を残す。-v--volumes)を付けたときだけ volume も削除される。
依存だけ残したい開発フローでは -v を付けない運用にする。

この記事は役立ちましたか?