Docker開発環境構築 - React(TypeScript)

# 概要
最近React(Typescript)で環境構築する機会があり、ReactをDocker化する上で工夫したところをまとめました。

# 環境
```terminal
$ node -v
v18.16.1
$ npm -v
9.5.1
$ docker -v
Docker version 24.0.2, build cb74dfc
```

# 事前準備
1.  [Docker](https://www.docker.com/products/docker-desktop/)をダウンロードして起動までしておきます。
2.  [React](https://create-react-app.dev/docs/getting-started#creating-a-typescript-app)のアプリケーションを作成しておきます。

# フォルダ構成

create-react-appでの自動生成物の中から不必要なフォルダやファイルを削除した構成になります。

```terminal
$ tree -I "node_modules"
```
```
.
├── node_modules (空を用意する。理由は後ほど説明します。)
├── Dockerfile
├── README.md
├── docker-compose.yml
├── package-lock.json
├── package.json
├── public
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.tsx
│   └── index.tsx
└── tsconfig.json
```

# Docker関係のファイルの内容

```dockerfile:Dockerfile
FROM node:18-alpine
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
RUN npm install
USER node
```
```yml:docker-compose.yml
version: '3.8'
services:
  node:
    build:
      context: .
    restart: always
    tty: true
    environment:
      NODE_ENV: development
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
      - node_modules:/usr/src/app/node_modules
volumes:
  node_modules:
    driver: local
    driver_opts:
      type: none
      device: ${PWD}/node_modules
      o: bind

```
```yml:.dockerignore
.git
node_modules
```

## 実行するコマンド
```console
$ docker-compose up -d
```
イメージとコンテナ生成。
imageサイズは500.76MBでした。
```console
$ docker-compose exec node sh
```
コンテナの中に入ります。
```console
$ npm start
```
localhost:3000でアプリケーションの立ち上がりを確認します。
Reactのロゴをブラウザで確認できれば成功です。
# 工夫したところ

## 軽量なベースイメージを使用する
-alpineがついたimageはコンテナで利用することを想定して設計されたOSのため、標準版よりも軽量化されているalpine版を採用しました。

参考:[軽量なベースイメージを使用する](https://y-ohgi.com/introduction-docker/3_production/dockerfile/#_3)

## rootではないUSERを使用する
rootを使用するとセキュリティの低下や予期しない動作をする可能性があるため、ユーザを変更します。
nodeイメージにはnodeという名前のユーザとグループがデフォルトで存在しているのでnodeを使用しました。コンテナ内で確認するとユーザー名の変更が確認できます。
```
/usr/src/app $ whoami
node
```

参考:[Dockerfile リファレンスの USER コマンド](https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#user)

## .dockerignoreを作成して余計なビルドをなくす
ビルドに余計な時間がかかってしまわないようにdockerで無視するファイルを指定しました。
指定する際は必要なファイルのみ指定します。

参考:[.dockerignore アンチパターン](https://qiita.com/munisystem/items/b0f08b28e8cc26132212)

## NODE_ENVにdevelopmentを指定する
今回は開発環境の構築のためdevelopmentを指定しました。
```yml:docker-compose.yml
environment:
      NODE_ENV: development
```

参考:[NODE_ENVにdevelopmentとproduction以外を入れると辛い](https://blog.p1ass.com/posts/node-env/)

## ビルドプロセスをスムーズにする
Dockerイメージ内でアプリケーションをビルドする際に必要な依存関係を事前に準備することで、ビルドにかかる時間を短縮させました。
```dockerfile:Dockerfile
COPY --chown=node:node package*.json ./
```

# 参考
[Dockerfileベストプラクティス - Node.jsアプリケーション編](https://qiita.com/taquaki-satwo/items/f8fbe8b1efc4b2323ae7)
[Dockerイメージ alpine,slim,stretch,buster,jessie等の違いと使い分け](https://www.ted027.com/post/docker-debian-difference/)
[docker run --restart=alwaysがどれくらいrestartしてくれるか調べた](https://qiita.com/manabuishiirb/items/f7edf11572f9256018b2)
[COPY とバインドマウントの使い分け](https://zenn.dev/suzuki_hoge/books/2022-03-docker-practice-8ae36c33424b59/viewer/3-5-bind-mount#copy-%E3%81%A8%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91)


# 感想
普段Dockerを使用する際はただコンテナの生成と削除を行っていただけだったのですが、自分で構築するとなるとさまざまなことを考慮しなければならず、思った以上にボリュームがありました。
やはり手を動かすことが大事ですね。。
お疲れ様でした。

# おまけ:空のnode_modulesを作成した理由
下記のvolumesで.:/usr/src/appと記述し、バインドマウントを設定していますが、ホスト側にnode_modulesが存在しない状態で`docker compose up -d`するとホスト側の情報がそのまま反映され、コンテナ内のnode_modulesがなくなってしまいます。
```yml:docker-compose.yml
version: '3.8'
services:
  gururi-node:
    build:
      context: .
    restart: always
    tty: true
    environment:
      NODE_ENV: development
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
      # 以下追記
      - node_modules:/usr/src/app/node_modules
# 以下追記    
volumes:
  node_modules:
```
バインドマウントとボリュームを同時に設定した際にボリュームの方が優先されるため、`node_modules`はホスト側の影響を受けることがなくなり、`node_modules`は削除されなくなります。しかし、`node_modules`はバインドマウントから除かれているため、ホスト側の`node_modules`が空になってしまいます。

ホスト側で`yarn install`することで解決するのですが、そんなことをしたらせっかくDockerで開発環境を整えた意味がなくなります。そのため下記のような追記をすることでボリュームで管理されている`node_modules`の内容をホスト側にも表示できるようになります。

```yml:docker-compose.yml
volumes:
  node_modules:
    # 以下追記   
    driver: local
    driver_opts:
      type: none
      device: ${PWD}/node_modules
      o: bind
```

参考:
[【Docker】コンテナ内のnode_modulesが消える問題を改善する](https://qiita.com/P-man_Brown/items/c2a69d943853cb381fbe)
[【Docker】ホスト側のnode_modulesが空になる問題を改善する](https://qiita.com/P-man_Brown/items/5a82ad98cb96206343b9)