🔥

一夜で組むFirebase×Cloud Run×FastAPI:AIアプリの土台づくり

に公開

はじめに

「まず動く“土台”だけ一晩で作りたい」──そんな想いで組み上げた記録です。

この記事では、筆者が 一夜で“アプリの土台”を組み上げたときの構成を、添付キャプチャ(アーキテクチャ図)をベースに解説します。

  • どのサービスが何を担当しているのか
  • 設定のポイント(ここを押さえると詰まりにくい)
  • 最小構成で安全に動かすための実務Tips
  • そして「土台はできたが、作り込みはこれから」という 今後の展望

まで、初学者が迷わない粒度でまとめます。

この記事は「土台づくり」が主題です。
UIの作り込み・分析ロジックの精度改善・プロンプト最適化・データ設計の深掘りなどは、これから育てていく前提で書いています。

この記事でわかること

  • 添付キャプチャの構成(Firebase Hosting / Auth / Storage / Firestore / Cloud Run / Vertex AI)の役割分担
  • Next.js(静的エクスポート)+ Firebase Hosting でフロントを最短で公開する方法
  • /api/* を Hosting から Cloud Run に rewrite して“APIだけ別”にする設計
  • Firebase Auth の IDトークン検証を FastAPI 側で行う最小実装
  • Firestore/Storage を使うときの 権限設計(Security Rules と IAM の境界)
  • Vertex AI(Gemini)を「Optional」にしておく理由と、あとから足すときのポイント

全体アーキテクチャ

まずは全体像です。

図1: Firebase Hosting(Next.js静的)→ /api/* を Cloud Run(FastAPI)へリライト。Auth/Storage/FirestoreをFirebaseでまとめ、必要ならVertex AI(Gemini)を足す。

リクエストの流れ(ざっくり)

  1. ユーザーは Firebase Hosting に配置した静的サイト(Next.js export)へアクセス
  2. フロントエンドは Firebase Auth(Google Sign-In) でログインし、IDトークンを取得
  3. フロントが /api/* を叩くと、Hosting の rewriteCloud Run(FastAPI) に転送される
  4. FastAPI は受け取った IDトークンを検証し、ユーザー(uid)を特定
  5. 必要に応じて
    • Firebase Storage から画像をダウンロード(または署名付きURL生成)
    • Firestore に分析結果・レポート・チャット履歴などを保存
    • Vertex AI(Gemini) で画像/テキストを解析(Optional)

なぜこの構成が「一夜で土台を作る」のに向いているのか

この構成の強みは、“迷いどころ”をマネージドサービスで潰せる点です。

  • フロント公開:Firebase Hosting(CDN付き)で即デプロイ
  • 認証:Firebase Auth(Google Sign-In)でID管理を丸投げ
  • API:Cloud Run に FastAPI を載せるだけ(スケールも運用も軽い)
  • データ:Firestore/Storage で“とりあえず動く”永続化がすぐできる
  • AI:Vertex AI を Optional にしておき、後から繋げられる

「最初から完璧」ではなく、“まず成立する形”を最短で作るのに向いています。

まず押さえる:サービス別の役割と設定ポイント(詳細)

ここからは、図1に出てくる各サービスを「概要 → できること → 設定のポイント → 落とし穴」の順で整理します。

1) Firebase Hosting(Next.js Static Export)

概要

静的ファイル(HTML/CSS/JS)をCDN配信できる Firebase のホスティング機能です。
今回は Next.jsを静的エクスポートして Hosting に置きます。

何ができるのか

  • 静的サイトの配信(キャッシュ・HTTPS・独自ドメイン対応)
  • /api/* のようなパスを Cloud Run へ rewrite(重要)
  • プレビュー環境(Preview Channels)で安全に試せる

設定のポイント

  • rewrite を最初から入れる
    「フロントは静的、APIは別」にすると設計がシンプルになります。
  • ルーティングは静的に寄せる(Next.js export前提)
    SSR/ISRに頼る設計だと export できずに詰まります。
  • キャッシュ制御(後回しにしがち)
    Cache-Control を雑にすると、更新が反映されない/逆にキャッシュが効かない、の両方が起きます。

firebase.json(Hosting + Cloud Run rewrite の最小例)

{
  "hosting": {
    "public": "out",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "/api/**",
        "run": { "serviceId": "agent-api", "region": "asia-northeast1" }
      }
    ]
  }
}

public は Next.js の export 先(例: out/)に合わせます。
serviceIdregion は Cloud Run 側と合わせてください。

落とし穴(よくある)

  • rewrite を入れたのに 404
    → Hosting 側の firebase deploy が反映されていない / region違い / serviceId違い
  • Next.js export で next/image が動かない
    → exportでは最適化が使えないことがあるので、unoptimized 設定を検討(後述)

2) Next.js(Static Export)

概要

Next.js を 静的サイトとして書き出して Hosting に置く方式です。

何ができるのか

  • SPA的な体験(ページ遷移・UI)を静的配信で実現
  • Firebase Auth を組み込み、ログイン状態で画面を切り替え
  • API呼び出しは /api/* で Cloud Run に逃がす

設定のポイント

  • Next.jsのバージョンにより export 方法が違うため、プロジェクトに合わせる
    • output: "export" を使う方式
    • next export を使う方式
  • export前提なら、画像最適化は unoptimized が安全なことが多い

例(next.config.js):

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "export",
  images: { unoptimized: true }
};

module.exports = nextConfig;

落とし穴

  • 動的ルート(例: /reports/[id])をそのまま使う
    → export向けに generateStaticParams などを考える or ルーティング方針を調整
  • 環境変数の扱い
    → 静的ビルドに埋め込まれるため、秘密情報は入れない(NEXT_PUBLIC_ のみ)

3) Firebase Auth(Google Sign-In)

概要

ユーザー認証を Firebase に任せる構成です。フロントでログイン → IDトークンを取得し、APIに渡します。

何ができるのか

  • Googleログイン(OAuth)の実装をほぼ省略
  • ユーザーID(uid)を一意に管理
  • セッション維持やトークン更新をSDKが面倒見てくれる

設定のポイント

  • Firebase Console で Google プロバイダを有効化
  • OAuth同意画面や承認済みドメイン設定(環境によって必要)
  • フロント → API は IDトークンを Authorization: Bearer で渡すのが定番

フロント(例:概念コード)

import { getAuth } from "firebase/auth";

async function callApi() {
  const auth = getAuth();
  const token = await auth.currentUser?.getIdToken();
  const res = await fetch("/api/health", {
    headers: { Authorization: `Bearer ${token}` }
  });
  return res.json();
}

落とし穴

  • 「動くけどセキュアじゃない」状態になりやすい
    API側でIDトークン検証して、uidベースでアクセス制御する
  • 開発中にCORSで詰まる
    → Hosting から叩くなら同一オリジンに寄るのでCORSは減る(ローカル時は別途対策)

4) Cloud Run(FastAPI / agent-api)

概要

FastAPI をコンテナ化して Cloud Run に載せる構成です。
Hosting の /api/* rewrite により、フロントからは“同じドメインのAPI”のように見えます。

何ができるのか

  • Python API を最小運用で公開(オートスケール)
  • Firebase Admin SDK / Firestore / Storage / Vertex AI などをまとめて叩ける
  • 重い処理(画像解析など)をフロントから分離できる

設定のポイント(ここが土台の肝)

  • コンテナは PORT を listen(Cloud Runの基本)
  • サービスアカウントを分けて最小権限にする(後述)
  • 公開方法は2択
    • まずは 未認証で公開して、アプリ側のIDトークンで守る(最短)
    • きっちりやるなら Cloud Run自体を認証必須にして invoker を絞る(後回しでもOK)

FastAPI の最小構成(IDトークン検証つき)

# main.py
from fastapi import FastAPI, Header, HTTPException
import firebase_admin
from firebase_admin import auth

# Cloud Run では ADC(サービスアカウント)で初期化できる想定
firebase_admin.initialize_app()

app = FastAPI()

def verify_bearer(authorization: str | None) -> dict:
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Missing bearer token")
    token = authorization.removeprefix("Bearer ").strip()
    try:
        decoded = auth.verify_id_token(token, check_revoked=True)
        return decoded
    except Exception:
        raise HTTPException(status_code=401, detail="Invalid ID token")

@app.get("/api/health")
def health(authorization: str | None = Header(default=None)):
    decoded = verify_bearer(authorization)
    return {"ok": True, "uid": decoded["uid"]}

Dockerfile(Cloud Run向けの最小例)

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV PYTHONUNBUFFERED=1
ENV PORT=8080

CMD ["uvicorn", "main:app", "--host=0.0.0.0", "--port=8080"]

落とし穴

  • uvicorn127.0.0.1 で起動していて疎通できない
    --host=0.0.0.0 が必須
  • Cloud Run の リージョンと Hosting rewrite の region がズレる
    firebase.json と Cloud Run を同じ region にする
  • トークン検証を入れたのに 401 が出る
    → フロントが token を付けていない / 古い token / Authorization ヘッダが落ちている

5) Firestore(Analysis / Reports / Chats )

概要

NoSQL(ドキュメントDB)で、アプリのデータをスピーディに持てます。
図1では「分析結果・レポート・チャット」を Firestore に保存する想定です。

何ができるのか

  • ユーザー別データをコレクションで管理しやすい
  • フロントから直接読み書き(リアルタイム)もできる
  • バックエンドから Admin SDK で集計・書き込みもできる

設定のポイント(“土台”で決めておくと後が楽)

  • ドキュメントパス設計を先に固定(後から移行がつらい)
    • 例:users/{uid}/photos/{photoId}
    • 例:users/{uid}/reports/{reportId}
    • 例:users/{uid}/chats/{threadId}/messages/{messageId}
  • 書き込み元を分ける
    • フロント:ユーザー操作の範囲(Security Rulesで制御)
    • バック:分析結果の確定書き込み(Admin SDKで実行)
  • インデックスは“必要になったら足す”でもOK(ただし詰まったら最優先で対応)

Security Rules(最小例)

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId}/{document=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

バックエンド(Admin SDK)は原則ルールをバイパスします。
だからこそ API側で uid を必ず検証して、他人のデータに触れないように実装します。

落とし穴

  • “楽だから”とトップレベルに何でも置く
    → 後から権限・クエリ・課金がつらくなるので、まず users/{uid}/... に寄せるのがおすすめ
  • 1ドキュメントが肥大化
    → チャットやログは「メッセージ単位でコレクション」に逃がす

6) Firebase Storage(Photos)

概要

画像などのバイナリを置くストレージです。
図1では Photos を Storage に置き、必要に応じて Cloud Run がダウンロードして解析します。

何ができるのか

  • フロントから直接アップロード(SDK + Security Rules)
  • バックエンドからダウンロード/解析
  • 署名付きURLで期間限定の配布もできる

設定のポイント

  • パス設計(Firestore同様に先に決める)
    • 例:users/{uid}/photos/{photoId}.jpg
  • 画像アップロードはフロント、解析はバック、という分業がわかりやすい
  • ライフサイクル(不要画像の削除)を後からでも入れられるようにしておく

Storage Rules(最小例)

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/photos/{allPaths=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

落とし穴

  • 画像を公開設定にしてしまう
    → 「とりあえず動く」けど後で事故る。まずはユーザー単位で閉じる。
  • Cloud Run からのアクセス権がない
    → Cloud Run の サービスアカウントStorage Object Viewer などを付与する(後述)

7) Vertex AI(Gemini)※Optional

概要

画像/テキスト解析を Google の生成AIで行う想定です。
ただし最初から入れると「課金・速度・安全性・プロンプト設計」が一気に難しくなるので、図1でも Optional にしてあります。

何ができるのか

  • 画像を含むマルチモーダル解析(例:食事写真 → 食材推定 → 栄養推定)
  • 文章生成(レポート作成、チャット応答)
  • 将来的に関数呼び出し/ツール連携などへ拡張

設定のポイント(後から繋ぐために“今”決めておくこと)

  • 呼び出しは Cloud Run 側に集約
    フロント直叩きは、キー管理や制限が難しくなりがちです。
  • “AIの入出力”を Firestore に ログとして残す設計にすると、後で改善しやすい
    • 入力(画像パス、ユーザーの指示)
    • 出力(推定結果、根拠、バージョン)
    • メタ(モデル名、温度、失敗理由)

落とし穴

  • いきなり本番データで回す
    → コストと情報漏洩リスクが読めないので、最初は「ダミーデータ + 上限付き」で回すのがおすすめ

“土台”を一夜で通すための手順(再現用)

ここからは「同じ土台を作るならこの順が早い」という並びで書きます。
(細部はプロジェクト事情で変わりますが、順序はかなり効きます)

Step 0. 事前準備(必要なもの)

  • Google Cloud プロジェクト(請求先設定)
  • Firebase プロジェクト(上と同一でOK)
  • ローカル環境
    • Node.js(Next.js + Firebase CLI)
    • Python(FastAPI)
    • gcloud CLI(Cloud Run deploy)
    • Firebase CLI

Step 1. Firebaseプロジェクトを作る

  1. Firebase Consoleでプロジェクト作成
  2. 「Webアプリ」を追加(Hosting用)
  3. Auth/Firestore/Storage を有効化

ここで作った firebaseConfig(APIキー等)は秘密情報ではありませんが、
“公開して良い設定”として扱い、環境を分けたい場合は projectId を切り替えます。

Step 2. Auth(Google Sign-In)を有効化

  • Authentication → Sign-in method → Google を有効化
  • 必要なら承認済みドメイン、OAuth同意画面を設定

この時点で、フロントでログイン→ユーザー情報が取れると “土台”の半分は完成です。

Step 3. Firestore / Storage のパス設計を決める

おすすめは users/{uid}/... に寄せる設計です。

例(今回の図に沿った叩き台)

  • users/{uid}/photos/{photoId}
  • users/{uid}/analysis/{analysisId}
  • users/{uid}/reports/{reportId}
  • users/{uid}/chats/{threadId}/messages/{messageId}

Step 4. Cloud Run(FastAPI)を先にデプロイして疎通する

  1. FastAPI の /api/health を作る(トークン検証は一旦オフでもOK)
  2. Dockerfile を作る
  3. Cloud Run にデプロイして curl で疎通

例(ローカルのターミナル)

gcloud run deploy agent-api \
  --source . \
  --region asia-northeast1 \
  --allow-unauthenticated

まずは最短で動かすため --allow-unauthenticated にしています。
実運用で絞る方法は後述します。

Step 5. Hosting から Cloud Run へ /api/* を rewrite

firebase.json の rewrites を設定して firebase deploy します。

firebase deploy --only hosting

これでフロントから /api/health が叩けるようになります。

Step 6. IDトークン検証を Cloud Run に入れる

  • フロント:Authorization: Bearer <token> を付与
  • バック:firebase_admin.auth.verify_id_token() で検証

ここまで通れば、「ログイン必須API」 の土台が完成です。

Step 7. Storage / Firestore の読み書きを足す

  • 画像は Storage
  • メタや分析結果は Firestore

に分けると扱いやすいです。

ここから先は「アプリ固有の作り込み」になるので、この記事では土台までに留めます。

IAM(最小権限)の考え方:どこで何を守るか

この構成は「Firebase(Rules)と Cloud Run(IAM/アプリ認証)」で守る場所が分かれます。

図2: 権限の境界(RulesとIAM)
図2: フロント直アクセスはRulesで制御、バックエンドはサービスアカウントに最小権限。uid検証はアプリで必須。

推奨の分担

  • フロント → Firestore/Storage:Security Rules でユーザー単位に制限
  • Cloud Run → Firestore/Storage/Vertex AI:Cloud Run のサービスアカウントに最小権限
  • Cloud Run の API 認証:Firebase IDトークン検証(これが主砦)

Cloud Run サービスアカウント(例)

agent-api-sa のような専用SAを作り、必要な権限だけ付けます。

  • Firestore:Cloud Datastore User(用途により Admin も)
  • Storage:Storage Object Viewer(ダウンロードのみなら)
  • Vertex AI:Vertex AI User(呼び出すなら)

役割名は組織ポリシーで変わることがあるため、まずは「読み取りだけ」「必要になったら昇格」が安全です。

実務Tips:一夜で作った“あと”に効く、壊れにくい工夫

1) 「環境分離」を早めに入れる(dev/stg/prod)

  • Firebase プロジェクトを分ける(または firebase use --add で切り替え)
  • Cloud Run サービスも agent-api-dev / agent-api-prod のように分ける
  • Firestore/Storage のデータが混ざるのが一番辛いので、早めに分けるのがおすすめ

2) APIは最初から「ログ」を残す

Cloud Run の Cloud Logging を前提に、最低限これをログに残すと後で助かります。

  • request id(自前 or Cloud Run の trace)
  • uid
  • 処理時間
  • 失敗理由(例外内容は機密を含めない)

3) AIは“後から足す”前提でインターフェースを切る

Vertex AI を Optional にするなら、例えばこう分けると後で差し替えやすいです。

  • POST /api/analyze:入力(画像パス等)を受け取り、analysisId を返す
  • バックグラウンド処理(後で Cloud Tasks / PubSub / Workflows に拡張)
  • GET /api/analysis/{analysisId}:結果参照

一夜で作る段階では同期でもOKですが、将来非同期にできる形にしておくと伸びます。

トラブルシュート(よくある詰まり)

/api/* が 404 になる

  • firebase.json の rewrite が入っているか
  • firebase deploy --only hosting したか
  • Cloud Run の serviceId / region が一致しているか

401(Missing/Invalid ID token)

  • フロントが Authorization を付けているか
  • ログイン直後で currentUser がまだ null になっていないか
  • バック側が Bearer 文字列を正しく剥がしているか

Storage/Firestore の権限エラー

  • フロントからのアクセス:Rules を見直す
  • Cloud Run からのアクセス:サービスアカウントの IAM を見直す

Vertex AI が 403 / quota

  • Vertex AI API が有効化されているか
  • リージョンとモデルが合っているか
  • サービスアカウント権限とクォータを確認

今後の展望(ここから“アプリ”に育てる)

一夜で作ったのは、あくまで「土台」です。ここから先は、プロダクトの価値を作り込むフェーズになります。

  • Firestore のスキーマを固める(履歴・集計・検索性)
  • 画像解析の精度を上げる(プロンプト、前処理、評価)
  • チャットUX(スレッド、文脈、要約)
  • 通知(毎朝のレポート、リマインド)
  • コスト最適化(Cloud Run min instances、AI呼び出し制御)
  • セキュリティ(App Check、レート制限、監査ログ、データ保持)

このあたりは別記事で深掘りする予定です。

まとめ

  • Firebase Hosting + Next.js static export で最短でフロント公開
  • /api/* rewrite → Cloud Run(FastAPI) でAPIを分離し、拡張しやすい
  • 認証は Firebase Auth(Google Sign-In)、APIは IDトークン検証が主軸
  • 画像は Storage、メタ/結果は Firestore で持つと見通しが良い
  • Vertex AI(Gemini)は Optional にして、土台が固まってから足すのが安全

参考リンク(一次情報)

Discussion