Appearance
デプロイ運用ガイド (Runbook)
「何を変えたら、どこを反映すれば本番に届くのか」を 1 枚で把握するためのページ。 個別の詳細手順は Terraform 設計/運用・開発フロー/マイグレ・ 踏んだ落とし穴 に譲り、ここでは 責務の境界と判断フローに絞る。
大原則: 何を誰が管理するか
| レイヤー | 管理するもの | 唯一の管理者 | 反映トリガー |
|---|---|---|---|
| Web (apps/web) | フロント | Netlify | main push で自動 build/deploy |
| API イメージ | コンテナ image | GitHub Actions (deploy-staging.yml) | main push で gcloud run deploy --image |
| API の env / secret | ALLOWED_ORIGINS / DATABASE_URL / Stripe 等 | Terraform (infra/terraform) | terraform apply (手動) |
| インフラ全般 | Cloud SQL / Secret Manager / IAM / WIF / AR | Terraform | terraform apply (手動) |
| DB スキーマ | テーブル定義 | drizzle migration | 手動 (Cloud SQL Auth Proxy 経由) |
最重要原則
Cloud Run の env と secret は Terraform が唯一の管理者。CI の gcloud run deploy は --image だけ更新し、env には一切触らない (既存 revision の env を引き継ぐだけ)。
→ env を変えたら必ず terraform apply。gcloud run deploy --set-env-vars で 個別に上書きしてはいけない (Secret Manager 参照が平文に化けて壊れる)。
変更種別ごとの「やること」
| 変更したもの | 必要なアクション | 自動 / 手動 |
|---|---|---|
apps/web/** | git push | Netlify が自動 |
apps/api/** (コードのみ) | git push | CI が image を deploy |
packages/* (db / shared) | git push (web は依存変更検知に注意 → 落とし穴⑯) | CI / Netlify |
Cloud Run の env (cloud_run.tf / tfvars の allowed_origins・auth_base_url) | terraform apply | 手動 (忘れると起動失敗) |
| Secret 値 (Stripe / SendGrid 等) | gcloud secrets versions add <NAME>_<env> --data-file=- → Cloud Run 再 deploy (新 version を読ませる) | 手動 |
Cloud SQL 設定 (cloud_sql.tf) | terraform apply | 手動 |
| DB スキーマ | migration 生成 → Auth Proxy 経由で migrate (dev-workflow) | 手動 |
| 新しい env / secret 名をコードで読み始めた | cloud_run.tf の env / secrets.tf の app_secret_names に追加 → terraform apply | 手動 |
⚠ 今回の事故 (2026-05-21) と教訓
症状: Cloud Run デプロイが「コンテナが PORT=8080 で listen できず起動失敗」。
真因: CORS の fail-fast (ALLOWED_ORIGINS 未設定なら NODE_ENV=production で起動時に throw) をコードに追加し、cloud_run.tf にも ALLOWED_ORIGINS env 定義を追加したが、 terraform apply を回していなかった。そのため Cloud Run の実 revision には ALLOWED_ORIGINS env が無いまま → アプリが起動時 throw → クラッシュ。
envs/staging.tfvars には allowed_origins = ["https://staging--liga.netlify.app"] と 値は入っていた。足りなかったのは「apply する」という操作だけ。
教訓:
- env を要求するコード変更とインフラ側の env 設定は必ずセットで
terraform apply。 - CI (image deploy) と Terraform (env/infra) は責務が別。CI を直しても env は入らない。
gcloud run deploy --set-env-varsで逃げない (一度この方法を試みたが、Secret Manager 参照を壊すので撤回した)。
デプロイが起動失敗したときの復旧フロー
sh
# 1. ログで起動時の例外を確認
gcloud run services logs read liga-api-staging --region=asia-northeast1 --limit=50
# → "ALLOWED_ORIGINS が未設定です" 等の throw が出ていれば env 反映漏れ
# 2. Terraform の差分を確認 (env 追加などが出るはず)
cd infra/terraform
terraform init -reconfigure \
-backend-config="bucket=${PROJECT_ID}-tf-state" \
-backend-config="prefix=staging"
terraform plan -var-file=envs/staging.tfvars
# 3. 反映
terraform apply -var-file=envs/staging.tfvars
# 4. 新 revision に env が入ったか確認
gcloud run services describe liga-api-staging --region=asia-northeast1 \
--format='value(spec.template.spec.containers[0].env)'CI の image はそのまま使われる (Terraform は image を ignore_changes)。
本番 (prod) を立ち上げるときの想定
詳細手順は Terraform 運用 → prod を増やすとき を参照。同 GCP project に env=prod suffix で別リソースを立てる方針 (backend prefix を prod に切替)。
prod 化チェックリスト
- [ ]
envs/prod.tfvarsのauth_base_url/allowed_originsを本番ドメインに - [ ]
terraform init -reconfigure ... -backend-config="prefix=prod"で state を分離 - [ ] Cloud SQL を Private IP 化 —
cloud_sql.tfは現状 staging を0.0.0.0/0で公開 しており (Netlify が public IP 経由で繋ぐ都合)、prod ではこれをやめて Private IP + Serverless VPC Access に切り替える (未実装。下記「セキュリティ宿題」) - [ ]
deletion_protection = true(env=prod で自動) - [ ]
availability_type = REGIONAL(HA、env=prod で自動) - [ ]
cloud_run_min_instances >= 1(cold start 回避、warm) - [ ] Secret をprod 用の値で投入 (
BETTER_AUTH_SECRET等は staging と別値、Stripe は live key) - [ ] Cloud Run に Cloud Armor / API ゲートウェイ を被せる検討 (現状
allUsers公開) - [ ] LINE ログインを使うなら
secrets.tfのapp_secret_namesにLINE_CLIENT_ID/LINE_CLIENT_SECRETを追加 +cloud_run.tfに env、NEXT_PUBLIC_LINE_LOGIN_ENABLEDを Netlify env に (現状 Terraform 未登録なので prod でも LINE は無効のまま) - [ ] Netlify の prod context に env を投入 (落とし穴⑪⑭)
- [ ] Stripe Webhook を prod エンドポイントで再登録
セキュリティ宿題 (staging の現状リスク)
cloud_sql.tf で staging の Cloud SQL を authorized_networks = 0.0.0.0/0 で全世界に公開 している (Netlify=AWS Lambda が固定 IP を持たず、public IP 経由で繋ぐ都合)。
実際に外部 IP からの postgres ユーザへの brute force (短時間で数百回の認証失敗) を 観測済み。postgres デフォルトユーザは Terraform 管理外 (パスワード未設定) で晒されている。
対応の方向 (どれを採るかは別途判断):
- Private IP + Serverless VPC Access に移行し public IP を閉じる (本筋。Netlify からは Cloud SQL Auth Proxy or 別経路に)
authorized_networksを絞る (Netlify は IP 不定なので限定が難しい → 1 が現実的)- 当面の緩和:
postgresデフォルトユーザに強固なパスkeyを設定 / 無効化、攻撃元 IP の遮断
関連ドキュメント
- Terraform 設計と運用 — ファイル構成・初期セットアップ・cheatsheet
- 開発フロー / マイグレーション — コード変更〜DB マイグレの具体手順
- staging 立ち上げログ — 初回構築の Phase 0〜8
- 踏んだ落とし穴 — 構築時のハマりどころ備忘