ソフトウェアをリリースしました

かいふく という訪問介護の副業に特化した求人サイトを公開しています。

お近くの介護資格保有者にご紹介頂けると嬉しいです 👏

https://kai-fuku.com/

このサイトにはプロモーションが含まれます

Laravel+Inertia+ReactをECSで動かしたい! NginxとPHP-FPMのコンテナ分離

Laravel + Inertia (React/Vue) の組み合わせは、SPA(シングルページアプリケーション)の体験とサーバーサイド(Laravel)の書きやすさを両立できる、非常に強力な構成です。

しかし、ローカルのDocker Compose環境では問題なく動作しても、AWSのECS Fargateのような本番環境にデプロイしようとすると、いくつかの疑問に直面します。

  • 「NginxとPHP-FPMコンテナはどう連携させるのがベスト?」
  • fastcgi_pass ってローカルと設定を変える必要ある?」
  • 「InertiaでビルドしたReactのJS/CSSファイルは、結局どのコンテナに置くのが正解?」

この記事では、fastcgi_pass の基本から、ECS Fargateでの最適なコンテナ構成、そしてInertia/Reactプロジェクト特有の「静的ファイルの配置場所」問題までを、順を追って解説します。

fastcgi_pass とは?

NginxとPHP-FPMの「橋渡し」

まず基本のおさらいです。Nginxは高性能なWebサーバーですが、それ自体はPHPコードを実行できません。
一方、PHP-FPMはPHPコードを実行することに特化したプロセス(サーバー)です。

Nginxがクライアント(ブラウザ)から .php ファイルへのリクエストを受け取ったとき、そのリクエストをPHP-FPMに処理してもらう必要があります。

このとき、Nginxの設定ファイル(nginx.conf)で、「どのPHP-FPMに処理を依頼するか」の宛先を指定するのが fastcgi_pass ディレクティブです。

location ~ \.php$ {
    # ...
    # ↓ この一行が「橋渡し」の指定
    fastcgi_pass php-fpm-server:9000; 
}

 

やんやん

プログラマーとしてLEMP環境に主に生息しており、DevOps 的な立ち回りをしながらご飯を食べている当ブログの管理人のやんやんと申します。
最近はTmux使うのを辞めました。

ローカル開発 (Docker Compose) での構成

ローカル開発では、「1コンテナ1責務」の原則に従い、NginxとPHP-FPMを別々のコンテナとして docker-compose.yml で定義するのが一般的です。

# docker-compose.yml (抜粋)
version: '3'
services:
  # Nginxコンテナ
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./laravel-project:/var/www/html

  # PHP-FPMコンテナ
  php:
    build: . # PHP-FPMとLaravelコードを含むDockerfile
    volumes:
      - ./laravel-project:/var/www/html

 

この構成では、NginxコンテナとPHPコンテナは 別々のネットワーク空間 に存在します。Docker Composeが提供する内部ネットワークを介して、サービス名で通信します。

したがって、NginxコンテナからPHPコンテナへは、php というサービス名(ホスト名)を使ってアクセスします。

# nginx.conf (Docker Compose用)
location ~ \.php$ {
    # ...
    # 'php' サービス(コンテナ)の 9000番ポートに転送
    fastcgi_pass php:9000;
}

 

(※ このTCP/IP通信によるパフォーマンスやセキュリティの懸念は、コンテナ間通信がDockerの内部ネットワークに限定されていれば、実用上ほとんど問題になりません。)

【1タスク2コンテナ】本番 (ECS Fargate) での構成:

さて、本題のECS Fargateです。
ECSでは、docker-compose.yml に相当するものとして「タスク定義 (Task Definition)」を使います。

ここで重要なキーポイントは、「1つのタスク定義の中で、NginxコンテナとPHP-FPMコンテナの2つを定義する」ことです。

これら2つのコンテナは、1セットで「Webアプリケーションサーバー」として機能します。

ECS Fargateでのfastcgi_passはどうなる?

ECSの同一タスク内で実行されるコンテナは、同じネットワーク空間(ネットワークモード awsvpc)を共有します

これは、Nginxコンテナから見ると、PHP-FPMコンテナが「他人」ではなく、「自分自身(localhost)」として見えることを意味します。

したがって、ECS Fargate用のNginxイメージに含める nginx.conf の設定は、以下のようになります。

# nginx.conf (ECS Fargate用)
location ~ \.php$ {
    # ...
    # サービス名ではなく、localhost (127.0.0.1) を指定する
    fastcgi_pass 127.0.0.1:9000;
}

 

タスク定義のイメージ

タスク定義では、以下のように設定します。

  • コンテナ-A: php-fpm-container
    • イメージ: (Laravelコードを含むPHP-FPMイメージ)
    • ポートマッピング: なし (外部に公開する必要はないため)
  • コンテナ-B: nginx-container
    • イメージ: (設定済みのnginx.confと静的ファイルを含むNginxイメージ)
    • ポートマッピング: 80:80 (ALBからのトラフィックを受け取るため)

ALB(ロードバランサー)からのトラフィックはNginxコンテナが受け取り、必要に応じて localhost:9000 を介してPHP-FPMコンテナに処理を渡します。

Inertia/Reactのビルドファイルはどこに置く?

ここで、Inertia/React構成特有の問題に直面します。

「InertiaはLaravel(PHP)がビューを配信する仕組みだから、npm run build で生成された public/build フォルダは、PHP-FPMコンテナにだけ置けば良いのでは?」

これはよくある誤解ですが、パフォーマンスと責務分離の観点から、ビルドした静的ファイル(JS/CSS)はNginxコンテナに配置するのが正解です。

この理由を理解するために、Inertiaのページが読み込まれる2段階のフローを見てみましょう。

ステップ1: HTMLシェルのリクエスト (PHP-FPMが処理)

  1. ブラウザが https://example.com/dashboard にアクセスします。
  2. ALB → Nginxコンテナがリクエストを受け取ります。
  3. NginxはこれがPHPのリクエストだと判断し、fastcgi_pass 127.0.0.1:9000 を使ってPHP-FPMコンテナに転送します。
  4. PHP (Laravel) が起動し、Inertiaは app.blade.php をレンダリングしようとします。
  5. このとき、PHPは public/build/manifest.json を読み取り、HTMLに含めるべきJS/CSSのファイル名を特定します。
  6. PHPは以下のようなHTMLの「ガワ」を生成し、Nginx経由でブラウザに返します。

    <html>
    <head>
        <script src="/build/assets/app.12345.js" defer></script>
        <link rel="stylesheet" href="/build/assets/app.67890.css">
    </head>
    <body>
        <div id="app" data-page="..."></div>
    </body>
    </html>
    

ステップ2: 静的アセット(JS/CSS)のリクエスト (Nginxが処理)

  1. ブラウザは、ステップ1で受け取ったHTMLを解析します。
  2. /build/assets/app.12345.js/build/assets/app.67890.css が必要だ」と判断し、ブラウザは別途2回のリクエストをサーバーに送信します。
  3. このリクエストは、もはやPHPとは全く関係ありません。
  4. ALB → Nginxコンテナがこの2つのリクエストを受け取ります。
  5. Nginxは「自分の管理下(public フォルダ)にそのファイルがあるか?」を探します。
  6. Nginxコンテナに public/build/ 以下の実体ファイルが存在するため、NginxはPHP-FPMを起動することなく、それらの静的ファイルを超高速でブラウザに直接返します。

もし、NginxコンテナにJS/CSSファイルがなければ、このリクエストもPHP-FPMに転送されてしまい、「JSファイルを取得するためだけにLaravelを起動する」という深刻なパフォーマンスボトルネックが発生します。

CI/CDでのベストプラクティス

上記2ステップから、CI/CDパイプラインでDockerイメージをビルドする際は、以下の構成が最適です。

  1. npm run build を実行し、public/build ディレクトリを生成します。
  2. PHP-FPMイメージのビルド:
    • Laravelのコード(app/, routes/ など)をコピーします。
    • ステップ1のために public/build/manifest.json をコピーします。(リンク生成に必要)
  3. Nginxイメージのビルド:
    • nginx.conf をコピーします。
    • ステップ2のために public フォルダ全体index.php と、public/build 以下の 全てのJS/CSSファイル を含む)をコピーします。(静的アセットの配信用)

結論

manifest.json はPHP-FPMコンテナに、JS/CSSの実体ファイルはNginxコンテナに必要です。

両方のイメージに public フォルダ(または public/build)全体をコピーするのが、最もシンプルで確実な方法です。

まとめ

ECS Fargateで Laravel + Inertia + React 構成を動かすための要点をまとめます。

  1. 構成: 「1タスク2コンテナ(Nginx + PHP-FPM)」構成を採用します。
  2. fastcgi_pass: NginxからPHP-FPMへの通信は、同一タスク内のため fastcgi_pass 127.0.0.1:9000; を指定します。
  3. 静的ファイル: ビルドしたJS/CSSは、パフォーマンス最適化のためNginxコンテナに配置し、Nginxから直接配信させます。
  4. manifest.json: HTMLシェル生成のため、PHP-FPMコンテナにも manifest.json が必要です。

この構成により、PHP-FPMは動的処理に専念し、Nginxは静的ファイルの高速配信に専念するという、「1コンテナ1責務」のメリットを最大限に活かした、スケーラブルな本番環境を構築できます。

おすすめの記事