【Docker】コンテナ内のnode_modulesが消える問題を改善する

## はじめに
本記事はプログラミング初学者が学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。  
そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。  
間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。  

## `node_modules`の取り扱いには注意が必要
Dockerを利用して環境構築をする際には`node_modules`の取り扱いに注意する必要があります。  
ホスト側で`yarn install`などをしてしまうと下の記事のようにエラーが発生することがあります。  

https://qiita.com/P-man_Brown/items/db1996bdee33ee667741

また`yarn install`などを`Dockerfile`で実行した際に`node_modules`が削除されたりします。  


今回は、コンテナ内の`node_modules`が削除される問題の改善方法について記載します。
`Next.js`の環境で試したので`Next.js`で記載しますが他でも応用可能と思います。

## コンテナ内の`node_modules`が消える
前述したとおり、`yarn install`などを`Dockerfile`で実行した際に`node_modules`が削除されてしまうことがあります。  
これは、`Dockerfile`と`docker-compose.yml`を以下のように記述している場合に起こります。

```Dockerfile:Dockerfile
FROM node:16.14.2

ENV USER_NAME=myuser
ENV TZ=Asia/Tokyo

WORKDIR /myapp

COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

RUN adduser ${USER_NAME} && \
  chown -R ${USER_NAME} /myapp
USER ${USER_NAME}

EXPOSE 3000
CMD ["yarn", "dev"]
```

```docker-compose.yml
version: '3.9'
services:
  front:
    build: .
    volumes:
      - .:/myapp
    environment:
      NODE_ENV: development
    ports:
      - 3000:3000
```
`docker compose up`すると以下のようなエラーが出ます。

```:ターミナル
$ docker compose up
[+] Running 2/2
 ⠿ Network next_default    Creat...                        0.0s
 ⠿ Container next-front-1  Cre...                          0.1s
Attaching to next-front-1
next-front-1  | yarn run v1.22.18
next-front-1  | $ next dev
next-front-1  | /bin/sh: 1: next: not found
next-front-1  | error Command failed with exit code 127.
next-front-1  | info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
next-front-1 exited with code 127
```

原因は`docker-compose.yml`の`volumes`にあります。  
ここでは`volumes`で`.:/myapp`と記述し、バインドマウントを設定しています。  
バインドマウントはマウント時にホスト側の情報がそのまま反映され、そのあとコンテナ内で変更があればホスト側に反映されます。
よって、ホスト側に`node_modules`が存在しない状態で`docker compose up`するとホスト側の情報がそのまま反映されます。
するとコンテナ内の`node_modules`がなくなってしまいます。 

確認のために`docker-compose.yml`から`volumes`削除します。

```docker-compose.yml
version: '3.9'
services:
  front:
    build: .
    # volumesを削除
    environment:
      NODE_ENV: development
    ports:
      - 3000:3000
```

変更後に`docker compose down` → `docker compose build` → `docker compose run front ls -a`すると`node_modules`があることが確認できます。

```:ターミナル
$ docker compose run front ls -a
[+] Running 1/0
 ⠿ Network myapp_default  Created      0.0s
.  ..  node_modules  package.json  yarn.lock
```
これは、バインドマウントが設定されておらず、ホスト側の情報がコンテナ内に反映されないためです。

再度`docker-compose.yml`の`volumes`を記述し`docker compose down` → `docker compose build` → `docker compose run front ls -a`します。
するとホスト側の情報がそのまま反映され`node_modules`は存在しません。

```:ターミナル
$ docker compose run front ls -a
[+] Running 1/0
 ⠿ Network myapp-_default  Created                            0.0s
.                 .editorconfig        .gitignore       Dockerfile
..                .env                 .husky           docker-compose.yml
.DS_Store         package.json         yarn.lock        .git
```
バインドマウントを設定したことでホスト側の情報がそのまま反映されていることがわかります。

### 改善方法
改善するためには、バインドマウントに加えて`node_modules`に対してボリュームを設定します。  
`Dockerfile`と`docker-compose.yml`を以下のようにします。

```Dockerfile:Dockerfile
FROM node:16.14.2

ENV USER_NAME=myuser
ENV TZ=Asia/Tokyo

WORKDIR /myapp

COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

RUN adduser ${USER_NAME} && \
  chown -R ${USER_NAME} /myapp
USER ${USER_NAME}

EXPOSE 3000
CMD ["yarn", "dev"]
```

```docker-compose.yml
version: '3.9'
services:
  front:
    build: .
    # 以下追記
    volumes:
      - .:/myapp
      - node_modules:/myapp/node_modules
    environment:
      NODE_ENV: development
    ports:
      - 3000:3000
# 以下追記
volumes:
  node_modules:
```

上記のようにするとエラーは出なくなります。  

バインドマウントとボリュームを同時に設定した際に、ボリュームの方が優先されます。  
そのため、`node_modules`に関しては`docker compose up`した際にホスト側の影響を受けることがなくなります。  
`node_modules`が削除されないため、エラーが出ません。  

なお名前つきボリュームを利用していますが、匿名ボリュームでも同様に機能します。  
バインドマウントやボリュームの違いについては以下の記事をご参照ください。  

https://qiita.com/P-man_Brown/items/30fba7bb265a525c77ad

ただし、この方法では`node_modules`はバインドマウントから除かれるため、ホスト側の`node_modules`が空になってしまいます。  

これはホスト側でも`yarn install`することや（ホスト側が空になる状態であればホスト側の変更はコンテナ内に反映されません）、VSCodeで `Remote - Containers`を利用することでも対処できます。  
`Remote - Containers`の使用方法については以下をご参照ください。

https://qiita.com/P-man_Brown/items/298e34af3fd6d363e085

しかし、ホスト側で`yarn install`せずに`node_modules`の中身を表示させる方法があります。
こちらについては以下にまとめましたのでご参照ください。

https://qiita.com/P-man_Brown/items/5a82ad98cb96206343b9