【Docker】React マルチステージビルド実践ガイド: Nginx 配信まで
モダンなフロントエンド開発において、React アプリケーションをコンテナ化してデプロイするケースは非常に増えています。しかし、単にnodeイメージを使ってビルドしただけでは、イメージサイズが巨大になりがちです。
今回は、Docker のマルチステージビルドを活用し、以下の要件を満たすコンテナイメージを作成する方法を解説します。
-
軽量化: 実行環境に不要な
node_modulesやソースコードを含めない - 品質保証: ビルドプロセスの中でテストを実行し、失敗したらイメージ作成を中断する
- 高速配信: ビルド成果物を軽量な Nginx コンテナで配信する
マルチステージビルドとは?
マルチステージビルドは、1 つのDockerfileの中に複数の「ステージ(段階)」を定義する手法です。
開発ツールや依存関係が必要な「ビルドステージ」と、実際にアプリを動かすための最小限の「実行ステージ」を分けることで、最終的なイメージサイズを劇的に小さくできます。
また、ASキーワードを使ってステージに名前を付けることで、後のステージから特定のファイルだけをCOPY(抽出)することが可能になります。
完成した Dockerfile
まずは結論となるDockerfileの全体像です。
ここでは「ビルドステージ(Builder)」と「実行ステージ(Runner)」の 2 段階構成にしています。
# ---------------------------------------------------
# 1. Build Stage: テストとビルドを行う環境
# ---------------------------------------------------
FROM node:18-alpine AS builder
WORKDIR /app
# 依存関係のインストール
# キャッシュを効かせるためにpackage.json類を先にコピー
COPY package*.json ./
# ciコマンドを使うことで、lockファイルに忠実なインストールを行う
RUN npm ci
# ソースコードを全コピー
COPY . .
# テストが失敗した場合、Dockerのビルドプロセスはここで停止(失敗)する
RUN npm test -- --watchAll=false
# プロダクションビルドを実行(/app/build に成果物が生成される)
RUN npm run build
# ---------------------------------------------------
# 2. Run Stage: Nginxで静的ファイルを配信する環境
# ---------------------------------------------------
FROM nginx:1.25-alpine AS runner
# デフォルトの設定ファイルを削除
RUN rm /etc/nginx/conf.d/default.conf
# カスタム設定ファイルをコンテナ内にコピー
COPY nginx.conf /etc/nginx/conf.d
# Build Stageで作った成果物(/app/build)だけをコピー
# ソースコードやnode_modulesはここには含まれない
COPY --from=builder /app/build /usr/share/nginx/html
# 80番ポートの公開を明示(ドキュメンテーション用途)
EXPOSE 80
# Nginxをフォアグラウンドで実行
CMD ["nginx", "-g", "daemon off;"]
ポイントの解説
1. ビルドプロセス内でのテスト実行 (RUN npm test)
CI/CD パイプラインを組む際、ローカルでのテストだけでなく、コンテナビルド時にもテストを実行するのが安全です。
RUN npm test -- --watchAll=false
この行を入れることで、もしコードにバグがありテストが通らない場合は docker build コマンド自体がエラーで終了します。「壊れたアプリが誤ってコンテナイメージになってしまう」事故を未然に防ぐことができます。
2. 成果物だけを抽出する (COPY --from=...)
ここがマルチステージビルドの肝です。
COPY --from=builder /app/build /usr/share/nginx/html
--from=builder を指定することで、最初のステージで作られた /app/build ディレクトリの中身(HTML/CSS/JS の静的ファイル)だけを、Nginx の公開ディレクトリにコピーしています。
これにより、最終イメージには数百 MB にもなるnode_modulesが含まれません。 数十 MB 程度の非常に軽量なイメージが完成します。
Nginx の設定 (SPA 対応)
React のような SPA(Single Page Application)を Nginx で配信する場合、注意点があります。
ユーザーが /about などのパスに直接アクセスした際、Nginx はデフォルトで about.html を探そうとして 404 エラーになります。
すべてのリクエストを index.html に転送して、ルーティングを React 側に任せる設定が必要です。
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
# 実ファイルが存在しなければ index.html を返す(SPA対応)
try_files $uri $uri/ /index.html;
}
# エラーページの設定など(必要に応じて)
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
この nginx.conf を Dockerfile と同じディレクトリに置いてビルドしてください。
実行方法
イメージのビルドと起動は以下のコマンドで行います。
# ビルド(テストが実行され、成功すればイメージができる)
docker build -t my-react-app .
# コンテナの起動(localhost:8080 でアクセス可能)
docker run -p 8080:80 --rm my-react-app
まとめ:マルチステージビルドのメリット
今回の構成を採用することで、以下のメリットが得られました。
-
イメージサイズの大幅削減:
node:alpineベースだと数百 MB になるものが、nginx:alpineベースに静的ファイルだけを入れることで 20~40MB 程度に収まります。 - セキュリティ向上: ソースコードや開発用ライブラリが本番環境に残らないため、攻撃対象領域を減らせます。
-
デプロイの安全性:
npm testが通らない限りイメージが生成されないため、品質が担保されます。
Discussion