FastAPI + React(Vite) のプロジェクトを、GCP上で以下の構成で運用できるところまで持っていった時の手順メモです。
実際のプロジェクトID/URL/秘密情報は プレースホルダに置き換えています。
この記事でやること
- Backend: Cloud Run(FastAPI)
- Frontend: Cloud Run(Nginxで静的配信)
- DB: Cloud SQL for PostgreSQL(Private IP)
- ストレージ: Cloud Storage(ロゴ画像のアップロード先)
- CI/CD: Cloud Build(GitHubトリガーで自動デプロイ)
-
独自ドメイン/HTTPS: Cloud Run ドメインマッピング(例:
example.jp/api.example.jp)
前提
- GCPプロジェクト作成済み(例:
<PROJECT_ID>/<PROJECT_NUMBER>) -
gcloudログイン済み - リージョンは例として
asia-northeast1 - Dockerでローカル起動できること
構成図(Mermaid)
1. GCP初期設定
export PROJECT_ID="<PROJECT_ID>"
export REGION="asia-northeast1"
gcloud config set project "$PROJECT_ID"
gcloud config set run/region "$REGION"
API有効化
gcloud services enable \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
secretmanager.googleapis.com \
sqladmin.googleapis.com \
compute.googleapis.com \
servicenetworking.googleapis.com \
iam.googleapis.com \
logging.googleapis.com \
monitoring.googleapis.com \
vpcaccess.googleapis.com \
deploymentmanager.googleapis.com
2. Artifact Registry(コンテナ保存先)
gcloud artifacts repositories create "<AR_REPO>" \
--repository-format=docker \
--location="$REGION"
3. Cloud SQL(PostgreSQL) + Private IP
インスタンス作成
gcloud sql instances create "<SQL_INSTANCE>" \
--database-version=POSTGRES_15 \
--tier="<DB_TIER>" \
--region="$REGION"
VPC + Private Service Access + VPCコネクタ(Cloud Run→Cloud SQLをPrivate IPで接続)
gcloud compute networks create "<VPC_NAME>" --subnet-mode=custom
gcloud compute networks subnets create "<SUBNET_NAME>" \
--network="<VPC_NAME>" --range="10.10.0.0/24" --region="$REGION"
gcloud compute addresses create "<PSA_RANGE>" \
--global --purpose=VPC_PEERING --prefix-length=16 --network="<VPC_NAME>"
gcloud services vpc-peerings connect \
--service=servicenetworking.googleapis.com \
--network="<VPC_NAME>" \
--ranges="<PSA_RANGE>"
gcloud sql instances patch "<SQL_INSTANCE>" \
--network="projects/$PROJECT_ID/global/networks/<VPC_NAME>"
gcloud compute networks vpc-access connectors create "<VPC_CONNECTOR>" \
--network="<VPC_NAME>" --region="$REGION" --range="10.8.0.0/28"
DB/ユーザー作成
gcloud sql databases create "<DB_NAME>" --instance="<SQL_INSTANCE>"
gcloud sql users create "<DB_USER>" --instance="<SQL_INSTANCE>" --password="<DB_PASSWORD>"
4. Secret Manager(DATABASE_URL)
DATABASE_URL は Secret Manager に入れて Cloud Run から参照します。
パスワードに + や = が入るとURLとして壊れる場合があるので、URLセーフな文字列推奨です。
DB_HOST="<CLOUD_SQL_PRIVATE_IP>"
DB_NAME="<DB_NAME>"
DB_USER="<DB_USER>"
DB_PASSWORD="<DB_PASSWORD>"
echo -n "postgresql://$DB_USER:$DB_PASSWORD@$DB_HOST:5432/$DB_NAME" \
| gcloud secrets create "<SECRET_DATABASE_URL>" --data-file=-
5. Cloud Storage(ロゴ保存先)
組織ポリシーでHMACキー作成が禁止されていたため、S3互換(HMAC)ではなく GCS SDKでアップロードする実装にしました。
export LOGO_BUCKET="<LOGO_BUCKET>"
gcloud storage buckets create "gs://$LOGO_BUCKET" \
--location="$REGION" \
--uniform-bucket-level-access
# ロゴが公開情報なら objectViewer を allUsers へ
gcloud storage buckets add-iam-policy-binding "gs://$LOGO_BUCKET" \
--member="allUsers" \
--role="roles/storage.objectViewer"
6. Cloud Run(backend)
実行用サービスアカウント作成(推奨)
RUN_SA="<RUN_SA_NAME>"
RUN_SA_EMAIL="$RUN_SA@$PROJECT_ID.iam.gserviceaccount.com"
gcloud iam service-accounts create "$RUN_SA" --display-name="Cloud Run SA"
gcloud secrets add-iam-policy-binding "<SECRET_DATABASE_URL>" \
--member="serviceAccount:$RUN_SA_EMAIL" \
--role="roles/secretmanager.secretAccessor"
gcloud storage buckets add-iam-policy-binding "gs://$LOGO_BUCKET" \
--member="serviceAccount:$RUN_SA_EMAIL" \
--role="roles/storage.objectAdmin"
ビルド&デプロイ
gcloud builds submit ./backend \
--tag="$REGION-docker.pkg.dev/$PROJECT_ID/<AR_REPO>/backend:latest"
gcloud run deploy "<BACKEND_SERVICE>" \
--image="$REGION-docker.pkg.dev/$PROJECT_ID/<AR_REPO>/backend:latest" \
--region="$REGION" \
--service-account="$RUN_SA_EMAIL" \
--allow-unauthenticated \
--port=8000 \
--vpc-connector="<VPC_CONNECTOR>" \
--vpc-egress=private-ranges-only \
--set-secrets="DATABASE_URL=<SECRET_DATABASE_URL>:latest" \
--update-env-vars="GCS_BUCKET=$LOGO_BUCKET,GCS_PUBLIC_BASE=https://storage.googleapis.com,GCS_LOGO_PREFIX=logos/"
7. マイグレーション(Cloud Run Job)
アプリ起動時に毎回マイグレーションは避け、Jobで実行します。
gcloud run jobs create "<MIGRATE_JOB>" \
--image="$REGION-docker.pkg.dev/$PROJECT_ID/<AR_REPO>/backend:latest" \
--region="$REGION" \
--service-account="$RUN_SA_EMAIL" \
--vpc-connector="<VPC_CONNECTOR>" \
--vpc-egress=private-ranges-only \
--set-secrets="DATABASE_URL=<SECRET_DATABASE_URL>:latest" \
--command="alembic" --args="upgrade,head"
gcloud run jobs execute "<MIGRATE_JOB>" --region "$REGION" --wait
8. Cloud Run(frontend)
Viteの VITE_API_URL はビルド時に埋め込むため、Cloud Buildで --build-arg を渡してビルドします。
BACKEND_URL="https://<BACKEND_BASE_URL>"
gcloud builds submit ./frontend \
--config=frontend/cloudbuild.yaml \
--substitutions=_REGION="$REGION",_VITE_API_URL="$BACKEND_URL"
gcloud run deploy "<FRONTEND_SERVICE>" \
--image="$REGION-docker.pkg.dev/$PROJECT_ID/<AR_REPO>/frontend:latest" \
--region="$REGION" \
--allow-unauthenticated \
--port=8080
9. CI/CD(GitHub → Cloud Build Trigger)
backend/cloudbuild.yaml と frontend/cloudbuild.yaml を用意し、GitHub連携トリガーで自動デプロイします。
ハマりどころ
-
poetry.lock を
.gitignoreしていると、Cloud Build側でCOPY poetry.lockが失敗する-
!backend/poetry.lockを追加して、backendのlockはコミットする運用にする
-
- Cloud Build実行SAが Cloud Run実行SAを使うには
iam.serviceaccounts.actAsが必要-
roles/iam.serviceAccountUserを付与
-
- トリガーで
build.service_accountを指定している場合、ログ設定が必要になることがある-
options.logging: CLOUD_LOGGING_ONLY/options.default_logs_bucket_behavior: REGIONAL_USER_OWNED_BUCKETを設定
-
10. 独自ドメイン/HTTPS(Cloud Runドメインマッピング)
例:
-
example.jp→ frontend -
api.example.jp→ backend
Search Console(ドメイン所有権)確認後、Cloud Runのドメインマッピングが提示する A/CNAME をDNSへ設定します。
おわりに
Cloud Run + Cloud SQL + GCS + Cloud Build + 独自ドメインまで、比較的低い運用コストで本番運用できる構成になりました。
同様の構成を作る際の参考になれば幸いです。