要約
- React アプリを Dockerize するなら、Docker のマルチステージビルドを利用して nginx イメージに載せることで、イメージサイズを 1GB -> 22MB にできる。
はじめに
React アプリを作ってみようと思い立ち、ローカルの開発環境では動かせるようになった。だが、これを本稼働させるにはどうするのがスマートなのか調べてみた。
React の公式ドキュメントでは、第一の方法として、次のように serve を使うのが簡単だよと書いてあるが、これだと実行環境にも Node.js が必要になる。
npm install -g serve
npm run build
serve -s build
Dockerize
運用しているほかの Web アプリは Docker 上で稼働させているので、React アプリも Docker イメージ化(Dockerize)して稼働させてみることにした。
まずは、DockerHub の node.js イメージから公式ドキュメントをたどってみたが、実践的な Dockerfile の書き方については情報なし。
そんな中、まさに的中の次の記事を見つけた。
方法
React アプリを Docker イメージ化して稼働させる方法として次の3つが考えられる。
- 開発デバッグ用と同様に npm start で手っ取り早く稼働させる。
- React の公式ドキュメントの通り、serve を使って稼働させる。
- 先の記事にある Production のように、nginx イメージにビルド結果を載せて稼働させる。
1.の方法では、js ファイルの minify もされないため、開発デバッグ用ならともかく、本番環境用とは言い難い。
2.の方法では、本番環境と同様にパッケージ化されて稼働するが、その Docker イメージには Node.js の実行環境に加えて、packages.json の dependencies に指定されているパッケージ(node_modulesフォルダ)も含まれてしまう。そのため、イメージサイズが大きい。
3.の方法ならイメージサイズもコンパクトになると期待できる。
実装
React アプリのルートフォルダに下記 Dockerfile と docker-compose.yml を用意し、
docker-compose up -d
コマンドを実行することでアプリ稼働。-
方法3 のポイントは Docker のマルチステージビルド。詳細は次のドキュメント参照。
https://docs.docker.com/develop/develop-images/multistage-build/
Dockerfile では、
npm install
とnpm run build
を別々に実行してレイヤを分けることで、依存パッケージに変更なければイメージビルド時のキャッシュが働き、ビルド時間を短縮。
Dockerfile
ARG NODE_TAG=12
ARG NGINX_TAG=alpine
ARG APP_HOME=/home/node/app
# build stage
FROM node:${NODE_TAG} as build
ARG NODE_TAG
ARG APP_HOME
WORKDIR ${APP_HOME}
COPY package*.json ${APP_HOME}/
RUN echo -n 'node '; node -v; \
echo -n 'npm '; npm -v ; \
npm install --production
COPY src ${APP_HOME}/src/
COPY public ${APP_HOME}/public/
RUN npm run build
# deploy stage
FROM nginx:${NGINX_TAG}
ARG NGINX_TAG
ARG APP_HOME
RUN echo "nginx:${NGINX_TAG}" > /docker-image-tag && cat /docker-image-tag
COPY --from=build ${APP_HOME}/build /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
docker-compose.yml
version: '3'
services:
app:
container_name: reactapp
image: reactapp:latest
build:
context: .
args:
- NODE_TAG=12.8
- NGINX_TAG=alpine
environment:
- NODE_ENV=production
ports:
- 8080:80
restart: always
試行・比較
(方法1) 1.03 GB
$ docker-compose build
$ docker images | grep reactapp
reactapp latest f951bf73549b About a minute ago 1.03GB
(方法2) 1.03 GB
$ docker-compose build
$ docker images | grep reactapp
reactapp latest 8abace7aab5c About a minute ago 1.03GB
(方法3) 22 MB
$ docker-compose build
$ docker images | grep reactapp
reactapp latest d07dcfbb1e54 About a minute ago 21.7MB
SSL/TLS 対応
上記の Dockerfile では React アプリのポートを直接公開してしまっているが、実際には、SSL/TLS 対応のリバースプロキシ配下に配置するなどして、セキュリティへの考慮も必要。
リバーズプロキシ配下で Context Path(Relative Path)付き URL とする場合は、このドキュメントに従って packages.json に
"homepage": ".",
を追加しておく。
{
"name": "reactapp-dockerized",
"homepage": ".",
"version": "0.1.0",
"private": true,
"dependencies": {
...
}
参考:試行環境
項目 | バージョン |
---|---|
node.js | 12.8.1 |
npm | 6.10.2 |
Docker | 19.03.1 |
docker-compose | 1.24.0 |
Docker Host OS | Debian 9.9 |