VSCode&Docker Volumeにおけるnode_modules問題を解決する

3 min read読了の目安(約3300字

Dockerでnodejs環境を構築するとき、node_modulesの扱いにはいくつかの嵌りポイントがあったので、それらの対抗策を考えてみました🐚

  1. .dockerignoreでnode_modulesがイメージにコピーされないようにする
  2. Volume Trickでnode_modulesがバインドマウントされるのを回避する
  3. VSCodeでnode_modulesを参照できない問題を解決する

今回は例として、nest.jsを使っていますが、他のプロジェクトでも基本は同じです!

.dockerignoreでnode_modulesがイメージにコピーされないようにする

まず、node_modulesはDockerイメージにホストからコピーしてはいけません。node_modulesはgitでも無視するはずなので、新しく環境構築する人のディレクトリにはそもそも入ってませんし、イメージをリビルドするときにホストのnode_modulesからイメージ内のnode_modulesが上書きされることも避ける必要があるからです。
なので、Dockerfileと.dockerignoreは例えば以下のようになるでしょう。

Dockerfile
FROM node:14-alpine

WORKDIR /workspace

COPY package*.json ./

RUN yarn install

COPY . .
.dockerignore
node_modules
npm-debug.log
yarn-error.log
dist

Volume Trickでnode_modulesがバインドマウントされるのを回避する

開発環境におけるコンテナの起動はdocker-compose.ymlで管理するのが便利なので、まずは一通り書いてみましょう。先程のイメージと、mysqlのイメージを利用することを考えます。

docker-compose.yml
version: '3'
services:
  nestjs:
    build: .
    tty: true
    restart: always
    command: yarn start:dev
    ports:
      - '3000:3000'
+   volumes:
+     - .:/workspace
    depends_on:
      - mysql
  mysql:
    image: mysql:8.0
    restart: always
    ports:
      - '3306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: development
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker

このときハイライト部分に注目してください。これはホスト(.):コンテナ(/workspace)でバインドマウントがなされており、互いの変更が反映されるようになります。しかし、これでは、node_modulesもホストとコンテナで同期されてしまうことになります。せっかくnodejsのバージョンを14で固定したのに、ホスト側のnode_modulesがバインドマウントされてしまっては環境破壊🔥になってしまいます。
そのため、以下のように匿名ボリュームをnode_modulesに適用してください。このようにすることでnode_modulesをホストから切り離しつつ、コンテナが破棄されてもvolumeとして保持することができるようです。この記事によればVolume Trickと言うようです。

docker-compose.yml
  volumes:
    - .:workspace
+   - /workspace/node_modules

VSCodeでnode_modulesを参照できない問題を解決する

これで問題は全て解決と思いきや、(当然ですが、)ホスト側のnode_modulesは空っぽです。その結果、「モジュールが見つからないよ〜」的なエラーがvscodeから吐かれることとなります。
解決方法として以下のような方法が挙げられる(記事などあった)んですが、

  • コンテナの中でvimとかで開発する ← VSCodeで開発したいんじゃ
  • ホストでもyarn installする ← Dockerでせっかく開発環境整えた意味なくなるんじゃ

ということで

Remote ContainersというVSCode拡張機能を使うことで、もうコンテナ内でVSCode使っちゃおうぜ

という方法がベストではないかと思います。Remote Containersとは、Dockerfileもしくはdocker-compose.ymlを読み取って、コンテナを起動して、その中で作業しちゃうことができる代物です。Microsoft製なので動作も信用できます。

そして、用意するのはこれまでに作成したものと、.devcontainer.jsonという設定ファイルのみです。Dockerfileで動作させることもできますが、今回はdocker-compose.ymlを用いる方法を紹介します。

.devcontainer.json
{
  "name": "NestJS",
  "dockerComposeFile": "./docker-compose.yml",
  "service": "nestjs",
  "workspaceFolder": "/workspace",
  "extensions": [
    "graphql.vscode-graphql",
    "dsznajder.es7-react-js-snippets",
    "xabikos.javascriptsnippets"
  ],
  "shutdownAction": "stopCompose"
}

詳しい説明は省きますが、extentionsでVSCodeの拡張機能をセットしたり、shutDownActionでVSCode終了時のアクションを設定できます。serviceは中に入るコンテナを指定するので、一つしか指定できませんが、ちゃんとmysqlコンテナも起動してくれます。

では、一番左下の緑色のボタンをクリックして、Open Folder In Containerから.devcontainer.jsonのあるディレクトリを選択すると起動してくれます。無事起動できれば完了です。

また、コンテナ→ホストへの反映を早くするために、docker-compose.ymlのバインドマウント部分を以下のようにしておくのもおすすめです。

docker-compose.yml
  volumes:
-   - .:/workspace
+   - .:/workspace:cached
    - /workspace/node_modules

私の環境で再度検証しましたが、もし、うまくいかないなどあればコメントで教えてくださいませ!