Skip to content

Database changes through GitHub Actions: staging first, then production - #10

Merged
TC960 merged 2 commits into
merge-finalfrom
ci/supabase-migrations
Oct 23, 2026
Merged

TC960 merged 2 commits into
merge-finalfrom
ci/supabase-migrations

Conversation

@TC960

@TC960 TC960 commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Database changes now go through GitHub Actions: every change lands on the staging project first and reaches production only if staging succeeded. Until now, migrations were pasted into the SQL editor by hand.

What's in it

  • Baseline. supabase/migrations/20261022140000_baseline.sql is the shared schema captured from production with pg_dump --schema-only. It includes the portal tables, the check-in system's tables and functions (which were never in a repo), triggers, row-level security, grants, our storage policies on resumes, and the attendees Realtime membership. The four hand-applied migrations move to supabase/archive/, since their effect is in the baseline. 20261022140100_baseline_data.sql adds the private resumes bucket and the DH26 event row if they're missing; neither is applicant data.
  • Proven on staging. Both migrations were applied to the empty staging project (gpoqlosneoicpzrluilj) with supabase db push. Staging and production were then compared: structure, grants (84 lines), storage policies and Realtime are identical, and supabase db lint finds no errors.
  • Grants match production. A fresh project's default privileges give anon and authenticated access to every new object; production had revoked some, such as release_decisions and add_organizer for anon. The baseline resets each object's grants before applying production's exact ones.
  • supabase-ci (PRs):
    • Rule check: existing migrations can't be edited (archiving into a baseline is allowed), and new ones must sort last.
    • New migrations are applied to staging inside a transaction that's then rolled back, so staging keeps matching main and abandoned PRs leave nothing behind.
    • Edge functions are type-checked with Deno.
    • Fork PRs still get the rule and type checks.
  • supabase-deploy (merge to main, one run at a time):
    • Staging: db push, lint, then deploy send-email and send-wallet-passes.
    • Production (environment supabase-production): dry run, db push, then deploy the functions.
    • Production is locked until the bootstrap below sets SUPABASE_PROD_BOOTSTRAPPED=true.
  • supabase-drift (nightly): fails if production's schema snapshot differs from staging's, which catches hand edits in the dashboard. It compares structure only, never data.
  • supabase-backup (nightly): pg_dump of production (public and auth), encrypted with BACKUP_PASSPHRASE before upload and kept 7 days. The repo is public, so only the encrypted file is uploaded. It skips with a warning until the secret exists.
  • Config and docs:
    • supabase/config.toml: project id, Postgres 17, and verify_jwt = true for each function. It's never used for config push.
    • supabase/functions/deno.json: so the functions type-check in CI.
    • The workflow and the steps below are written up in docs/application-portal-todo.md.

Tested locally:

  • the migration rules and the rolled-back trial against staging, with a good migration, a broken one, an edited one and an out-of-order one
  • deno check on all three functions
  • tsc and vitest (78 tests pass)
  • action-validator on all four workflows

The workflows themselves haven't run on GitHub yet.

Before this deploys anything to production

  1. Bootstrap production's migration history. Production already has everything in the baseline but no record of it. This writes the record only and runs no SQL:
    npx supabase@2.121.0 migration repair --status applied 20261022140000 20261022140100 \
      --db-url "postgresql://postgres.yrxjhrrphhbmqejresvy:<PASSWORD>@aws-0-us-west-1.pooler.supabase.com:5432/postgres"
    gh variable set SUPABASE_PROD_BOOTSTRAPPED --repo ucsdds3/datahacks --body true
    
  2. Add the BACKUP_PASSPHRASE secret: a long random passphrase, also kept in the team password manager.
  3. Create the supabase-production Environment (Settings → Environments), limited to main.
  4. Protect main: require PRs and the supabase-ci checks, since a merge now changes production.
  5. Tell the check-in repo that its SQL changes come here as migrations from now on.

🤖 Generated with Claude Code

TC960 and others added 2 commits October 22, 2026 22:16
…uction

- Baseline: the shared schema captured from production (public schema with
  the check-in tables, functions, triggers, policies and grants, plus our
  storage policies and Realtime membership) in one migration; the four
  hand-applied migrations move to supabase/archive/. A second migration adds
  the resumes bucket and the DH26 event row if missing. Both applied to the
  new staging project, which then matched production exactly: schema, grants,
  storage policies and Realtime.
- Grants are reset per object before production's exact grants, because a
  fresh project's default privileges would otherwise give the API roles more.
- supabase-ci (PRs): migration rules, new migrations tried on staging in a
  rolled-back transaction, edge functions type-checked.
- supabase-deploy (main): staging push, lint and functions, then production
  dry run, push and functions; production stays locked until
  SUPABASE_PROD_BOOTSTRAPPED is set after the one-time history repair.
- supabase-drift and supabase-backup run nightly; the backup is encrypted with
  BACKUP_PASSPHRASE before it leaves the runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
…et already ran

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
@TC960
TC960 merged commit 5489940 into merge-final Oct 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant