🐅

【Docker】React マルチステージビルド実践ガイド: Nginx 配信まで

に公開

モダンなフロントエンド開発において、React アプリケーションをコンテナ化してデプロイするケースは非常に増えています。しかし、単にnodeイメージを使ってビルドしただけでは、イメージサイズが巨大になりがちです。

今回は、Docker のマルチステージビルドを活用し、以下の要件を満たすコンテナイメージを作成する方法を解説します。

  1. 軽量化: 実行環境に不要なnode_modulesやソースコードを含めない
  2. 品質保証: ビルドプロセスの中でテストを実行し、失敗したらイメージ作成を中断する
  3. 高速配信: ビルド成果物を軽量な Nginx コンテナで配信する

マルチステージビルドとは?

マルチステージビルドは、1 つのDockerfileの中に複数の「ステージ(段階)」を定義する手法です。
開発ツールや依存関係が必要な「ビルドステージ」と、実際にアプリを動かすための最小限の「実行ステージ」を分けることで、最終的なイメージサイズを劇的に小さくできます。

また、ASキーワードを使ってステージに名前を付けることで、後のステージから特定のファイルだけをCOPY(抽出)することが可能になります。

完成した Dockerfile

まずは結論となるDockerfileの全体像です。
ここでは「ビルドステージ(Builder)」と「実行ステージ(Runner)」の 2 段階構成にしています。

dockerfile
# ---------------------------------------------------
# 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 側に任せる設定が必要です。

nginx.conf
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

まとめ:マルチステージビルドのメリット

今回の構成を採用することで、以下のメリットが得られました。

  1. イメージサイズの大幅削減: node:alpineベースだと数百 MB になるものが、nginx:alpineベースに静的ファイルだけを入れることで 20~40MB 程度に収まります。
  2. セキュリティ向上: ソースコードや開発用ライブラリが本番環境に残らないため、攻撃対象領域を減らせます。
  3. デプロイの安全性: npm test が通らない限りイメージが生成されないため、品質が担保されます。

Discussion