概要
最近React(Typescript)で環境構築する機会があり、ReactをDocker化する上で工夫したところをまとめました。
環境
$ node -v
v18.16.1
$ npm -v
9.5.1
$ docker -v
Docker version 24.0.2, build cb74dfc
事前準備
フォルダ構成
create-react-appでの自動生成物の中から不必要なフォルダやファイルを削除した構成になります。
$ 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関係のファイルの内容
FROM node:18-alpine
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
RUN npm install
USER node
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
.git
node_modules
実行するコマンド
$ docker-compose up -d
イメージとコンテナ生成。
imageサイズは500.76MBでした。
$ docker-compose exec node sh
コンテナの中に入ります。
$ npm start
localhost:3000でアプリケーションの立ち上がりを確認します。
Reactのロゴをブラウザで確認できれば成功です。
工夫したところ
軽量なベースイメージを使用する
-alpineがついたimageはコンテナで利用することを想定して設計されたOSのため、標準版よりも軽量化されているalpine版を採用しました。
rootではないUSERを使用する
rootを使用するとセキュリティの低下や予期しない動作をする可能性があるため、ユーザを変更します。
nodeイメージにはnodeという名前のユーザとグループがデフォルトで存在しているのでnodeを使用しました。コンテナ内で確認するとユーザー名の変更が確認できます。
/usr/src/app $ whoami
node
参考:Dockerfile リファレンスの USER コマンド
.dockerignoreを作成して余計なビルドをなくす
ビルドに余計な時間がかかってしまわないようにdockerで無視するファイルを指定しました。
指定する際は必要なファイルのみ指定します。
NODE_ENVにdevelopmentを指定する
今回は開発環境の構築のためdevelopmentを指定しました。
environment:
NODE_ENV: development
参考:NODE_ENVにdevelopmentとproduction以外を入れると辛い
ビルドプロセスをスムーズにする
Dockerイメージ内でアプリケーションをビルドする際に必要な依存関係を事前に準備することで、ビルドにかかる時間を短縮させました。
COPY --chown=node:node package*.json ./
参考
Dockerfileベストプラクティス - Node.jsアプリケーション編
Dockerイメージ alpine,slim,stretch,buster,jessie等の違いと使い分け
docker run --restart=alwaysがどれくらいrestartしてくれるか調べた
COPY とバインドマウントの使い分け
感想
普段Dockerを使用する際はただコンテナの生成と削除を行っていただけだったのですが、自分で構築するとなるとさまざまなことを考慮しなければならず、思った以上にボリュームがありました。
やはり手を動かすことが大事ですね。。
お疲れ様でした。
おまけ:空のnode_modulesを作成した理由
下記のvolumesで.:/usr/src/appと記述し、バインドマウントを設定していますが、ホスト側にnode_modulesが存在しない状態でdocker compose up -d
するとホスト側の情報がそのまま反映され、コンテナ内のnode_modulesがなくなってしまいます。
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
の内容をホスト側にも表示できるようになります。
volumes:
node_modules:
# 以下追記
driver: local
driver_opts:
type: none
device: ${PWD}/node_modules
o: bind
参考:
【Docker】コンテナ内のnode_modulesが消える問題を改善する
【Docker】ホスト側のnode_modulesが空になる問題を改善する