Docker Compose の bind mounts から node_modules を除外する方法
Docker Compose において bind mounts から Node.js のパッケージ格納ディレクトリである node_modules
を除外する方法についてです。
主な使いどころとしては、開発環境において「 bind mounts を利用したいが node_modules
ディレクトリはビルド時以外は触らない場合」をイメージしています。
前提
以下のバージョンを前提としています。
- Node.js 16.x
- NPM 7.x
- Docker 20.10.8
- Docker Compose v2.0.0
バージョンが異なるとこの方法が使えないかもしれないので、参考にされる際は注意してください。
方法 A. node_modules
を bind mounts 対象ディレクトリの外に移動する
早速結論ですが、 node_modules
を bind mounts の対象ディレクトリの外に配置すれば OK です。
Node.js / NPM は node_modules
の配置として複数の場所をサポートしているのでその機能を利用します。
この点に関して Node.js の公式ドキュメントに次のような説明があります。
For example, if the file at
'/home/ry/projects/foo.js'
calledrequire('bar.js')
, then Node.js would look in the following locations, in this order:
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
意訳:
たとえば、 '/home/ry/projects/foo.js'
にあるファイルが require('bar.js')
を行った場合、 Node.js は次の場所を上から順に探索します:
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
Loading from node_modules
folders | Modules: CommonJS modules | Node.js Documentation
つまり、プロジェクトディレクトリの他に、ユーザーのホームディレクトリや /home
ディレクトリ、ルートなど node_modules
の置き場所として利用できます。
これらのうち Docker で利用する場合最もかんたんなのはルートの /node_modules
に配置する形でしょうか。
どこで設定するかですが、通常 node_modules
の中身はビルド時に確定するため、単純に Dockerfile
の側で丸ごと移動してしまえば OK かと思います。
イメージは次のとおりです。
src/Dockerfile
:
FROM node:16
WORKDIR /app
COPY package*.json ./
# パッケージをインストール後 `node_modules` を `/node_modules` に移動する
RUN npm install && \
mv ./node_modules /
CMD ["npm", "run", "develop"]
docker-compose.yml
:
version: "3"
services:
app:
build:
context: ./src
volumes:
# こちらでは `node_modules` が含まれていないディレクトリを bind mount するだけ
- ./src:/app:cached
ports:
- "8000:8000"
何かしらの理由で /node_modules
の利用が難しい場合は、ホームディレクトリなど別の場所の利用を検討するとよいかと思います。
方法 B. named volume を重ねて除外する
「どうしても node_modules
を bind mounts 対象ディレクトリの中に置いておきたいが、 node_modules
は bind mounts から外したい」という場合は当然ながら上の 方法 A は使えません。
そのような場合は docker-compose.yml
の側で named volume を使って重ねるという迂回策が使えます。
イメージ:
docker-compose.yml
:
version: "3"
services:
app:
build:
context: ./src
volumes:
- ./src:/app:cached
# `node_modules` を bind mounts から除外する
- node_modules:/app/node_modules
ports:
- "8000:8000"
volumes:
node_modules:
この場合、 Dockerfile
の側ですべきことは特にありません。
この 方法 B のアプローチを採るときは、パッケージを更新したりしたとき(= node_modules
の中身が変わったとき)に、一度ボリュームを削除するなどの対応が必要になるためその点注意が必要です。
参考
- Loading from
node_modules
folders | Modules: CommonJS modules | Node.js Documentation - All together... | Modules: CommonJS modules | Node.js Documentation
- 'node.js - Docker-compose: node_modules not present in a volume after npm install succeeds - Stack Overflow
おまけ: Yarn の場合
パッケージマネージャに NPM ではなく Yarn を使う場合も同様のことが可能です。 しかし、(私が確認したかぎり) NPM の場合よりも少し複雑でした。
具体的には、 yarn install
の --modules-folder
オプションで node_modules
ディレクトリの配置を変更することができます。
ただし、そのままでは変更後の node_modules/.bin
内のコマンドを Yarn が認識してくれません。
パッケージが提供するコマンドを使用する場合は環境変数 PATH
もあわせて変更する必要がありました。
FROM node:16
WORKDIR /app
# Update Yarn.
RUN curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
COPY package.json yarn.lock ./
# Yarn は `/node_modules/.bin` を認識してくれないので PATH に追加
# See: https://github.com/yarnpkg/yarn/issues/1684
ENV PATH=/node_modules/.bin:$PATH
# パッケージをプロジェクトディレクトリではなく `/node_modules` に格納する
RUN yarn install --modules-folder=/node_modules
CMD ["npm", "run", "develop"]
ただし、 Yarn についてはかんたんに動作確認しただけで細かく検証したわけではありません。 参考にされる際はその点ご留意ください。