はじめに
TRIAL&RetailAI Advent Calendar 2025の6日目になります。
昨日は@fujithuroさんの『未来の自分を泣かせないコミットメッセージ』でした。最近はClaude Codeなど生成AIにコミットさせていますが、コミットメッセージに意思決定を含められるようにしたほうが良いなと考えさせられる良記事でした。
本日は『リバースプロキシの良さと必然性を再確認する』というテーマで書きたいと思います。
普段はアプリケーションコードを書くことがメインですが、WebアプリをCloud Runへデプロイする構成を検討する中で、「コードが正しくても、ネットワーク構成次第では繋がらない」という壁に当たりました。
特にCloud Run バックエンドを「Internal(VPC内部からのみアクセス許可)」にしている場合、フロントエンドは静的配信だけではバックエンドにアクセスできず、リバースプロキシ構成が必須になるという点が重要です。この記事では、Webアプリのデプロイ方法として、この違いを分ける次の2つの構成を比較して整理します。
🖼️ 1枚まとめ
🔧 設計対象の構成
今回の前提は、バックエンドを外部インターネットから隔離してセキュアに保つことです。
利用したコードはこちらに置いています。
バックエンド (Python/FastAPI)
- デプロイ先: Cloud Run
- Ingress: Internal (VPC からのみアクセス可能)
- 認証: 未認証を許可 (allUsers許可済み)
- ポイント: 外部(パブリックIP)からは直接アクセスできない状態です。
フロントエンド (Flutter Web)
- デプロイ先: Cloud Run
- 設定: 内部通信のためにDirect VPC Egressを経由するように設定しています。
この環境で、以下の2つのデプロイ方法を比較して整理しました。
① 静的ファイル配信のみ (素のnginx)
最初の構成は、単純にFlutter Webのビルド成果物をnginxで配信するだけのものです。この場合、APIリクエストはブラウザから直接バックエンドにリクエストが送られる構造になります。
# ... (ビルドステップ省略)
FROM nginx:alpine
# ... (設定省略)
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
② リバースプロキシ付きnginx (Cloud Run内でバックエンドへ接続)
もうひとつの構成はnginxにリバースプロキシを置き、Cloud Runコンテナ内部からバックエンドに接続するパターンです。
# ... (ビルドステップ省略)
##########################################
# Deploy with Nginx (with reverse proxy)
##########################################
FROM nginx:alpine
# Copy Nginx configuration as template
COPY nginx.conf /etc/nginx/nginx.conf.template
# Copy entrypoint script
COPY docker-entrypoint.sh /docker-entrypoint.sh
# ... (設定省略)
CMD ["/docker-entrypoint.sh"]
nginx.conf の設定例:
# ... (設定省略) ...
http {
upstream backend {
# docker-entrypoint.sh で実際のホスト:ポートに置換
server BACKEND_HOST_PLACEHOLDER:BACKEND_PORT_PLACEHOLDER;
keepalive 32;
}
server {
listen 8080;
client_max_body_size 10M;
# ... (静的ファイルのlocation設定) ...
location /api/ {
# Cloud Runのルーティングに必要なHostヘッダ等を設定
proxy_set_header Host BACKEND_HOST_PLACEHOLDER;
# クライアント情報の転送
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_request_headers on;
# パフォーマンス設定
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_pass BACKEND_SCHEME_PLACEHOLDER://backend/api/;
}
}
}
💡 実装の工夫
docker-entrypoint.shを使って環境変数からバックエンドの接続情報を埋め込むことで、環境ごとにnginx.confを書き換えなくて済むようにしています。
⚠️nginxの仕様上の注意
今回のようにupstreamディレクティブを使用する場合、名前解決はnginx起動時に行われ、そのIPアドレスがキャッシュされます。
Cloud RunのIPアドレスが変更された場合、変更に追従するにはコンテナの再起動が必要になります。
🔍 この2方式の本質的な違い
| フロント構成 | バックエンド(internal) に到達できるか | 通信の主体 |
|---|---|---|
| ① 静的配信のみ | ❌ 到達不可 | ブラウザ(外部ネットワーク) |
| ② リバースプロキシ付き | ✅ 到達可 | Cloud Run コンテナ(VPC 内部) |
① 静的配信のみ ❌ 到達不可
ブラウザからのリクエストは外部ネットワーク扱いになるため、Internalなバックエンドには届きません。
② リバースプロキシ付き ✅ 到達可
nginxが中継することで、「VPC内のコンテナからのアクセス」に変わります。これでバックエンドにリクエストが届くようになります。
🎯 メリットと注意点
✅ メリット
-
バックエンドを守れる安心感
バックエンドをInternalに閉じることで、意図しない外部からのアクセスをネットワークレベルで遮断できます。 -
CORS設定が楽になる
ブラウザからは「同じドメインの/api」にアクセスしているように見えるため、CORSエラーに悩まされることがなくなります。
⚠️ 注意が必要な点
-
セキュリティは「合わせ技」で
Internalにしたからといって、フロントエンド(nginx)経由でAPIは叩けてしまいます。バックエンド側での認証実装は変わらず重要だと再認識しました。 -
Cloud Run間の認証
今回はバックエンドの設定で「未認証の呼び出しを許可」していますが、VPC内を信頼する前提の構成です。より厳密にIAM認証を行う場合は、nginx側でトークン処理を追加するなどの工夫が必要になります。
🤔 ロードバランサは使わないの?
「ロードバランサを手前に置いてルーティングすれば、Nginxなしでも実現できるのでは?」と思われた方もいるかもしれません。 確かにロードバランサのURLマップを使えば可能ですが、今回は以下の理由からNginx構成を採用しています。
- コスト効率: ロードバランサは固定費がかかりますが、Nginx on Cloud Runならリクエスト数に応じた従量課金のみで済みます。
-
ポータビリティ:
nginx.confに設定を集約することで、ローカル開発環境でもdocker-compose等を使って本番と全く同じ構成を再現できます。
📦 まとめ
Cloud RunでバックエンドをInternalにする場合、フロントエンドにも一工夫(リバースプロキシ)が必要だということが、今回の構築を通じてよく理解できました。アプリケーション開発者としては、普段あまり意識しないnginxの設定ですが、クラウド環境での構成を考える上では「通信の主体」を意識することが大切だと改めて感じました。同じような構成を検討されている方の参考になれば幸いです。
明日は@10longさんの『【圏論×AI×認知科学】村上春樹「ノルウェーの森」をLLMで形式化してみた』です。お楽しみに!(こちらの動画が参考になるかも...??)
TRIAとRetail AIはエンジニアを募集しています。




