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 より優先させる のが定石。
よくある原因
- bind mount の上書き: ホストに
node_modulesが無い状態で./:/appをマウントすると、/app/node_modulesが空ディレクトリで覆われる - イメージ層で
npm install済み: Dockerfile でRUN npm ciしていても、ランタイムの bind mount に上書きされて意味を失う - OS / arch 非互換: ホスト(macOS arm64)の
node_modulesを Linux コンテナで使うとesbuild/sharpなどのネイティブバイナリが動かない - 匿名ボリュームの揮発:
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 ci4. down -v の挙動を理解する
docker compose down は named volume を残す。-v(--volumes)を付けたときだけ volume も削除される。
依存だけ残したい開発フローでは -v を付けない運用にする。