Skip to content

開発フロー (staging への反映 / マイグレーション / 巻き戻し)

「コードを書いてから staging に反映されるまで」と「DB を変えるとき」の手順。

結論サマリ

  • apps/web だけ変更main push で Netlify が auto deploy
  • apps/api だけ変更main push で GHA (deploy-staging.yml) が Cloud Run へ deploy
  • packages/* だけ変更 → Cloud Run には反映されるが Netlify は build skip するので、netlify deploy --build --prod を手動で叩く
  • DB スキーマ変更pnpm -F @liga/db generate でマイグレ生成 → commit → main push で GHA (deploy-staging.ymlmigrate ジョブ) が deploy 前に 自動適用 (追加系のみ自動。破壊的マイグレはガードがブロックし手動適用)
  • Secret 変更gcloud secrets versions add ... → Cloud Run を再 deploy (or gcloud run services update --update-secrets)

1. 通常のコード変更フロー

1-a. apps/web/** を変更

sh
# 編集 → commit
git add apps/web/...
git commit -m "feat(web): ..."
git push origin main

これだけ。Netlify が GitHub 連携で main push を検知し、apps/web の build を起動 → production context で deploy。約 2-3 分。

確認:

sh
cd apps/web
netlify status                            # current site = liga-staging
netlify open                              # ブラウザで Netlify dashboard を開く
curl -i https://liga-staging.netlify.app/ # HTTP 200 確認

1-b. apps/api/** を変更

sh
git add apps/api/...
git commit -m "feat(api): ..."
git push origin main

GitHub Actions の deploy-staging.yml が main push をトリガに起動 → Docker build → Artifact Registry へ push → Cloud Run service liga-api-staging を新 image で update。約 3-5 分。

確認:

sh
gh run watch --exit-status  # 完了まで CLI で監視
gh run list -L 3 -w deploy-staging.yml
curl -i https://liga-api-staging-2cu6gfwvvq-an.a.run.app/healthz

1-c. packages/* (db / shared) だけを変更

ここが 注意ポイント

  • Cloud Run 側: Docker build が packages/* を含めるので、apps/api 変更がなくても GHA で deploy できる。ただし GHA は apps/api/** パス変更でしかトリガしない設定には なってない (現状は main push 全部で動く) のでそのまま push で反映される
  • Netlify 側: netlify.tomlbase = "apps/web" を見て build content change を 判定するので、packages/* だけの push は "Canceled build due to no content change" で skip される

対策:

sh
# 手動で Netlify build を発火
cd apps/web
netlify deploy --build --prod

詳細は 踏んだ落とし穴 ⑯

1-d. infra/terraform/** を変更

自動では走らない。ローカルから手動 apply

sh
cd infra/terraform
terraform plan  -var-file=envs/staging.tfvars   # 差分確認
terraform apply -var-file=envs/staging.tfvars -auto-approve

State は GCS バケットに保存されてるので他の人が apply した後でも terraform plan で差分 確認できる。

2. DB スキーマ変更

packages/db/src/schema/*.ts を編集する変更は マイグレーション生成 + 適用 の 2 step。

Step 1 — マイグレーション生成 (ローカル / dev DB 立ち上げ済み前提)

sh
# dev の Postgres を起動 (docker-compose)
pnpm dev:db:up

# schema を編集
$EDITOR packages/db/src/schema/tournaments.ts

# 差分から SQL を自動生成
pnpm -F @liga/db generate
#   → packages/db/drizzle/NNNN_<random>.sql が増える
#   → packages/db/drizzle/meta/NNNN_snapshot.json も更新される

# dev DB に適用して動作確認
pnpm -F @liga/db migrate    # DATABASE_URL は dev のローカル DB
pnpm -F @liga/db seed       # 必要なら seed 再投入

# commit
git add packages/db/
git commit -m "feat(db): add foo column to tournaments"

drizzle-kit push は使わない

push は schema を直接 DB に当てるだけでマイグレファイルを残さない。dev で個人実験するとき 以外は禁止。必ず generatemigrate で履歴に残す。

Step 2 — staging DB へ適用 (通常は自動 / 例外は手動)

通常は自動。 main push で deploy-staging.ymlmigrate ジョブが deploy-api前段 (needs: migrate) として走り、未適用マイグレを Cloud SQL Auth Proxy 経由で適用する。

  • 追加系 (ADD COLUMN 等) は自動適用 される
  • 破壊的マイグレ (DROP COLUMN/TABLE・型変更・TRUNCATE) はガードがブロック して migrate ジョブを fail させる (deploy も needs で止まる)。scripts/ci/check-destructive-migrations.sh が実 DB の未適用 (pending) 集合を drizzle.__drizzle_migrations_journal.json から 特定して検査する
  • 破壊的を承知で自動適用したいときは workflow_dispatchallow_destructive=true で実行する

ガードにブロックされた破壊的マイグレや prod は、下記の手動手順で適用する。

sh
# 1) Cloud SQL Auth Proxy をローカルで起動 (背景プロセス)
cloud-sql-proxy \
  --port 5433 \
  project-0fccf552-fc68-4756-9c1:asia-northeast1:liga-staging &

# 2) staging の DATABASE_URL を Secret から取得して Proxy 越しに繋ぐ形に書き換える
#    (Secret は postgres://liga:<pass>@/liga?host=/cloudsql/<conn> の Unix socket 形式)
DB_PASS=$(gcloud secrets versions access latest --secret=DATABASE_URL_staging | \
  sed -E 's|.*://liga:([^@]+)@.*|\1|')
export DATABASE_URL="postgres://liga:${DB_PASS}@127.0.0.1:5433/liga"

# 3) migrate
pnpm -F @liga/db migrate

# 4) Proxy を止める
kill %1

ローカル dev DB と Proxy のポート衝突

ローカル開発の Postgres も 5433 を使う (pnpm dev:db:up)。手動で Proxy を立てるときは ローカル DB を止めるか、Proxy を別ポート (例 --port 5434) にして DATABASE_URL も合わせる。

Step 3 — アプリ側 deploy (schema 変更とアプリ変更が両方ある場合)

  • 追加系は migrate ジョブが deploy より先に自動適用するので、順序は自動で守られる (新カラムが無い状態で新アプリが SELECT して落ちる事故を防ぐ)
  • 破壊的マイグレ (カラム削除等) は expand-contract で:
    • アプリ側で先にカラム参照を消して deploy
    • 確認後にカラム削除マイグレを書き、ガードのブロックを承知で手動適用 (or allow_destructive=true)

staging migrate の GHA 化 (実装済み)

deploy-staging.ymlmigrate ジョブを実装済み (deploy-apineeds: migrate)。要点:

  • WIF で GCP 認証 → Secret Manager DATABASE_URL_staging から接続情報取得 → Cloud SQL Auth Proxy (127.0.0.1:5433) 経由で pnpm db:migrate
  • proxy バイナリは公式 GCS の固定 SHA256 で検証。DB パスワードは ::add-mask:: でログマスク
  • 破壊的マイグレは scripts/ci/check-destructive-migrations.sh がブロック (上記)
  • IAM は github-actions-${env} SA の secretAccessor (DATABASE_URL_${env}) + project-level cloudsql.client を使用 (terraform 既存)

prod は自動 deploy パイプライン自体が無いため、prod の migrate は引き続き手動。

3. Secret 値の変更

*_staging Secret を上書き

sh
# 例: SENDGRID_API_KEY_staging を新しい値に
echo -n "SG.real-key-here" | \
  gcloud secrets versions add SENDGRID_API_KEY_staging --data-file=-

新しい version が latest になる。

Cloud Run に拾わせる

Cloud Run の secret_key_ref { version = "latest" }service revision 作成時点 で latest を解決する。なので Secret を更新しても自動では拾わない。次の deploy で拾う、もしくは:

sh
gcloud run services update liga-api-staging \
  --region asia-northeast1 \
  --update-secrets SENDGRID_API_KEY=SENDGRID_API_KEY_staging:latest

(同じ参照を --update-secrets で再指定するだけで新 revision が作られて latest を解決)

Netlify に拾わせる

sh
cd apps/web
netlify env:set FOO "bar" --context production --force
netlify deploy --build --prod

env 反映には 再 build が必要 (Functions のビルド成果物に env がスナップショットされるため)。

詳細 踏んだ落とし穴 ⑭

4. 巻き戻し (rollback)

Netlify (apps/web)

UI から: Deploys → 旧 deploy を選んで "Publish deploy"。

CLI から or git で:

sh
git revert <bad-sha>
git push origin main   # auto deploy

Cloud Run (apps/api)

sh
# 直近の revision 一覧
gcloud run revisions list --service liga-api-staging --region asia-northeast1

# 1 つ前の revision に traffic 100% を寄せる
gcloud run services update-traffic liga-api-staging \
  --region asia-northeast1 \
  --to-revisions liga-api-staging-00007-abc=100

Cloud Run は image 自体を AR に残してるので、git revert + push でも結果的に元に戻る (古い image を build し直す形)。緊急時は update-traffic のほうが速い。

DB マイグレーション

drizzle-kit は rollback 用 SQL を自動生成しない。下のいずれか:

  • 破壊的でないマイグレ (ADD COLUMN ... NULL) なら、アプリ側の git revert で済む (カラムは残るが使われない、後で DROP COLUMN マイグレを書く)
  • 破壊的なマイグレ (DROP COLUMN, ALTER ... TYPE) は 手書きで rollback SQL を別途用意 してから本番適用する

5. CI が通らないと merge / deploy できないようにする (将来の改善)

current: main 直 push 運用。事故を起こしやすい。

将来案:

  • main を branch protection で 「PR + CI green + 1 review approval」 必須に
  • ci.yml (typecheck + build) を required check に
  • staging deploy は main merge をトリガに継続

そうすると流れは:

ローカル編集 → PR → CI 緑 → review → main merge → 自動 staging deploy

6. dev サーバの立て方 (ローカル)

参考までに。

sh
# 依存
pnpm i

# dev DB (docker-compose で Postgres 17)
pnpm dev:db:up

# migrate + seed
pnpm -F @liga/db migrate
pnpm -F @liga/db seed

# web (Next.js) と api (Hono) を並列起動
pnpm dev
#   web : http://localhost:3100
#   api : http://localhost:3001

7. ローカルでメールを確認する (dry-run)

メール送信 (@liga/mailer、SendGrid SDK) は MAIL_DRY_RUN=true のとき実送信せず、 宛先 / 件名 / 本文 / 本文中のリンクを API のコンソールにダンプする。SendGrid の 設定 (API キー・Sender 認証) なしでメール周りを確認できる。

認証メール (確認 / パスワードリセット / メール変更) は apps/api が送る (認証は API に 集約済み) ので、ダンプは API ログに出る。

sh
# apps/api/.env.local に 1 行
MAIL_DRY_RUN=true
# → API を再起動 (tsx watch は env-file 変更を拾わないので必須)
sh
# 送信内容を見る (API を pnpm dev のログに出している前提)
grep -A8 "mailer DRY-RUN" <api>

# リアルタイム追尾 + メールだけ抽出
tail -f <api> | grep --line-buffered -A8 "mailer DRY-RUN"

出力例 (パスワードリセット):

[mailer DRY-RUN] to=... subject=[LiGA] パスワードリセットのご案内
...
http://localhost:3100/api/auth/reset-password/<token>?callbackURL=%2Freset-password%2Fconfirm

リンクは http://localhost:3100/... を指すので、コピーしてブラウザで開けばメール ゲートのフロー (リセット確定 / メール確認) まで完結確認できる

dev / staging / prod の方針

  • dev: apps/api/.env.localMAIL_DRY_RUN=true
  • staging: cloud_run.tfvar.env == "staging" のとき MAIL_DRY_RUN=true を投入 (実送信しない。ダンプは Cloud Run ログ)
  • prod: MAIL_DRY_RUN を入れない = 実送信。別途 SendGrid の API キー + Sender 認証 (Single Sender か Domain Authentication) が必要

MAIL_DRY_RUN を入れず SENDGRID_API_KEY も未設定だと、mailer は 黙って no-op (内容も出ない) になる点に注意。ローカル確認時は必ず MAIL_DRY_RUN=true を入れる。

関連