Skip to content

staging 構築で踏んだ落とし穴

「次に同じ構成を作る人が同じ罠を踏まないため」の備忘録。 全ての修正は最終的に commit / TF に反映済み。

gcloud projects update --update-labels が gcloud バージョンによって未対応

エラー: unrecognized arguments: --update-labels=...

対策: プロジェクトレベルのラベルは諦めて、per-resource ラベル (TF 側) だけにする。 billing report / Cloud Logging の絞り込みは per-resource ラベルで十分足りる。

② 新規 GCP project の Cloud SQL edition が ENTERPRISE_PLUS デフォルト

エラー: Invalid Tier (db-f1-micro) for (ENTERPRISE_PLUS) Edition

新規 project のデフォルトが ENTERPRISE_PLUS になっており、これだと shared-core (db-f1-micro / db-g1-small) や custom tier が使えず db-perf-optimized-N-* (最低 ~¥10,000/月) しか選べない。

対策: cloud_sql.tfsettings.edition = "ENTERPRISE" を明示。

③ Cloud Run の env block で予約名 PORT を指定して 400

エラー: reserved env names were provided: PORT

Cloud Run は ports.container_port から PORT を自動セットする。TF 側で env { name = "PORT" ... } を書くと拒否。

対策: env block から PORT を削除。apps/api/src/index.tsprocess.env.PORT ?? 3001 で fallback してくれる前提で、Cloud Run が自動セットする 8080 が使われる。

④ Secret に version が無い状態で Cloud Run deploy すると失敗

Cloud Run は secret_key_ref { version = "latest" } で参照するが、Secret slot だけ作って version を投入してない状態だと「latest が見つからない」エラー。

対策: TF で placeholder versionsecrets.tf で作る (secret_data = "REPLACE_ME_...")。 あとから gcloud secrets versions add で実値を投入すると latest がそちらを指す。

⑤ Terraform apply 中の yes 入力が拒否される

CLI が yes 入力を受け付けるはずなのに "Apply cancelled" になる。改行や前後の スペースが混じってる場合に発生。

対策: terraform apply -auto-approve フラグを使う。

gh secret set -e staging が 404

Environment が未作成なのに gh secret set -e で投入しようとして失敗。

対策: 先に gh api -X PUT repos/<owner>/<repo>/environments/staging で Environment を作る。

⑦ GitHub Actions if: で Environment variable が読めない

下の式を job 先頭に書き、変数を Environment level に設定したが、job の if: は environment が bind される前に評価されるため常に false → skip。

yaml
if: ${{ vars.ENABLE_STAGING_DEPLOY == 'true' }}

対策: ゲートを削除する (元々は事故防止用なので staging が安定したら不要)。 本当に必要なら repo level variable で設定。

⑧ Dockerfile の COPY で shell リダイレクト不可

dockerfile
COPY ... /node_modules ./node_modules 2>/dev/null || true

shell リダイレクトは RUN だけで使える。COPY だと「/2>/dev/null というファイルを コピーしようとする」と解釈されて fail。

対策: 削除。多段 COPY をやめて単純化。

⑨ Dockerfile の build stage で pnpm -F @liga/db build が無く fail

ERR_PNPM_RECURSIVE_RUN_NO_SCRIPTpackages/dbpackages/sharedsrc/*.ts を直接 export しているので build script を持たない。

対策: build を諦め、Cloud Run のランタイムで tsx を使って TS を直接解釈する 構成にした (シングルステージ Dockerfile + CMD ["./node_modules/.bin/tsx", "src/index.ts"])。 本番化時に esbuild bundling 等で再最適化する余地あり。

⑩ pnpm の bin が root の node_modules/.bin/ に置かれない

エラー: Cannot find module '/repo/node_modules/.bin/tsx'

pnpm は他の package manager と違って、root の node_modules/.bin/ にバイナリを ホイストしない (--shamefully-hoist 必要)。各 package の node_modules/.bin/ にだけ置く。

対策: Dockerfile の WORKDIR を apps/api にして ./node_modules/.bin/tsx で参照する。

⑪ Netlify env を --context branch-deploy で投入 → main deploy で反映されない

Netlify は main push を production context として扱う。branch-deploy context に投入した値は他ブランチ deploy にしか効かない。

対策: --context production を明示する (or Site 全体に 1 環境しか持たせない 意図なら --context を外しても OK だが、CLI のバージョンによって挙動が違うので 明示が無難)。

⑫ DATABASE_URL の Cloud SQL Unix socket 形式 (?host=/cloudsql/...) は Netlify で動かない

getaddrinfo ENOTFOUND /cloudsql/...。Unix socket は Cloud Run の Cloud SQL connector の機能で、Netlify (AWS Lambda) ではマウントされてない。

対策: Netlify 用には Cloud SQL public IP 形式 にする (postgres://user:pw@35.x.x.x:5432/liga?sslmode=...)。これに伴い:

  • Cloud SQL の authorized_networks0.0.0.0/0 を追加 (staging のみ、 TF の dynamic block で env=staging 限定)
  • DB password 漏洩しない限り保護 (32 文字乱数)
  • 本番は Private IP + Serverless VPC Access に切替予定

⑬ Cloud SQL の Google CA cert を Node が信頼しない

エラー: UNABLE_TO_VERIFY_LEAF_SIGNATURE

sslmode=require だと SSL を強制するが pg の default は cert verification あり。 Cloud SQL の自己署名 CA を Node の trust store が知らない → verification 失敗。

対策: ?sslmode=no-verify に変える (SSL 通信 = encrypted は維持、cert 検証だけ スキップ)。staging では妥協。本番は CA cert を pg config に渡す or Private IP に 切替で SSL 不要にする。

⑭ Netlify env 更新後は再 deploy が必要

netlify env:set してもすぐには反映されない。Functions のビルド成果物に env が スナップショットされる仕様 (たぶん)。

対策: netlify deploy --build --prod を明示的に叩く or 空 commit で auto-deploy を発火させる。

⑮ Cloud SQL の connection slot 枯渇 (pg 53300)

エラー (Netlify Function log):

error: remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role
code: '53300'

Cloud SQL db-f1-micromax_connections = 25 / instance に対し、pg.Pool({ max: 10 }) × 同時 warm container 数 で簡単に 25 を超える。production builds は RSC エラーの中身を 隠すので、ブラウザには generic な「Server Components render エラー」しか出ず、原因切り分けに 時間を食う。

切り分け手順:

sh
netlify logs --source functions --since 30m | grep -E '(ERROR|FATAL|connection)'

対策: packages/db/src/client.tsPool を Lambda 系前提に縮める。

ts
new Pool({
  connectionString: url,
  max: 1,                       // 1 container = 1 request なので 1 で足る
  idleTimeoutMillis: 10_000,    // idle hold を切る
  connectionTimeoutMillis: 5_000,// hang 回避
});

globalThis への pool キャッシュは production でも 効かせる (warm container 内で pool を再利用)。元コードは HMR 対策で dev のみキャッシュしてたが、Lambda 系ではむしろ production で必要。

副産物の罠: Netlify は apps/web 配下の変更で build trigger を判定するので、 packages/db だけ変更した push は "Canceled build due to no content change" で build を skip する。手動で netlify deploy --build --prod を叩くか、apps/web 配下に 空 commit を作る必要がある。

⑯ Netlify が monorepo の依存パッケージ変更を検知せず build skip

netlify.tomlbase = "apps/web" を指定してると、その配下のファイル変更しか "content change" として検知されない。packages/dbpackages/shared を直す push では Netlify が自動 deploy をスキップする。

対策案:

  • 都度 netlify deploy --build --prod を手動で叩く (現状はこれ)
  • apps/web に空 commit (touch apps/web/.deploy-tick 系) で trigger
  • netlify.toml[build.processing] ignore = "false" で全 push deploy 強制 (build 課金注意)
  • 将来は CI 側で「packages/* 変更時は Netlify deploy hook を叩く」を仕込む

まとめ — 構築の流れ (修正済みのフロー)

  1. CLI 一式インストール (gcloud / terraform / netlify-cli / cloud-sql-proxy / gh)
  2. GCP プロジェクト招待を受領 → gcloud config set project ...
  3. State 用 GCS バケット作成 (gcloud storage buckets create + update-labels)
  4. terraform initterraform apply -var-file=envs/staging.tfvars -auto-approve
  5. Secret 値投入 (BETTER_AUTH / Stripe test、他は TODO)
  6. Cloud SQL Auth Proxy 経由で pnpm migrate + seed
  7. GitHub Environment "staging" 作成 → Secrets / Variables 投入
  8. Netlify env vars を --context production で投入 (DATABASE_URL は public IP + sslmode=no-verify)
  9. main push or gh workflow run deploy-staging.yml で Cloud Run へ初回 deploy
  10. netlify deploy --build --prod で Netlify 再 deploy
  11. 動作確認 (HTTP 200)