Le Duy Khuong (Daniel)

Series: supabase-series · Part 10

Dev Productivity & Tools

Deploy, CI/CD and multi-project

Deploy migrations safely; CI/CD (secrets, branch); multiple projects dev/staging/prod; secure keys; deploy checklist.

2026-03-173 min read

Introduction

Final post in the series: deploy migrations safely, CI/CD (secrets, branch), multiple projects (dev / staging / prod) and secure keys. Apply environment separation, no logging of secrets, and a deploy checklist (manifest) when needed.

Goal: Have a consistent process for deploying migrations and app; clear dev/staging/prod split; keys and secrets never in repo or logs.

Features

Deploy migration

  • Local → cloud: After testing the migration locally (supabase db reset), link the project (staging first): supabase link --project-ref <staging-ref>, then supabase db push. Verify the app runs correctly on staging before pushing to production.
  • Rollback: There is no automatic "migration rollback"; to undo, you have to write a new migration (ALTER/DROP). Back up the DB before pushing large or risky migrations.

CI/CD

  • Secret: Put SUPABASE_SERVICE_ROLE_KEY, JWT secret, and DB password in CI/CD variables (GitHub Secrets, GitLab CI variables); never store them in the repo. Scripts read only from env.
  • Branch: Typically main (or production) deploys to prod; other branches deploy to preview/staging. Migrations should run in the deploy step (e.g. supabase link + supabase db push) with secrets coming from CI.
  • Deploy checklist: Document the steps: build → run migration (staging then prod) → deploy app → smoke test. Call it a "deployment checklist" or "release runbook"; it helps you avoid skipping steps (e.g. forgetting to push a migration).

Multi-project

  • Model: One Supabase project per environment (dev, staging, prod) → different URLs and keys; the app in each env reads the right set of variables. Or one project with multiple branches (Supabase Branching) if you use that feature.
  • Shared vs dedicated: "Dedicated" = one project per app/microservice (separate DBs); "shared" = multiple apps using the same project. Choose based on your data isolation needs and cost.

Workflow / Process

  1. Preparation: The repo has supabase/migrations/; CI has SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY variables (or a DB URL for migrations) for each env.
  2. Merge PR: After merging into the corresponding branch, the pipeline builds and tests. Deploy step: link the project (ref from env) → supabase db push (run new migrations) → deploy the app (build, deploy serverless or container).
  3. Env order: Deploy to staging first; verify (manual testing or E2E); then deploy to production. Migrations always run before or together with deploying a schema-compatible app.
  4. Audit: Don't log URLs, keys, or tokens in CI logs; use pre-commit or CI checks to ensure no file containing service_role or a password gets committed.

Convention: A deploy checklist (steps, order, person responsible) for each release; secrets only in env / a secret manager.

Sample code

CI (GitHub Actions) — example migration deploy step

- name: Push migrations (staging)
  env:
    SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
  run: |
    npx supabase link --project-ref ${{ secrets.SUPABASE_STAGING_REF }}
    npx supabase db push
  # Don't echo the real token/ref to the log

Deploy checklist (markdown in the repo)

## Deploy production
1. Make sure staging runs cleanly with the new migration.
2. In CI: run the prod deploy job (link prod project, db push, deploy app).
3. Smoke test: login, create a resource, check Realtime if applicable.
4. If it fails: check logs (no secrets), roll back the app or handle the migration per the runbook.

Separating env in the app

# .env.staging
SUPABASE_URL=https://xxx-staging.supabase.co
SUPABASE_ANON_KEY=...
# .env.production
SUPABASE_URL=https://xxx-prod.supabase.co
SUPABASE_ANON_KEY=...

The app reads the right file based on NODE_ENV or the DEPLOY_ENV variable; don't hardcode URLs/keys.

Applying it in the architecture

  • Environment separation: Dev/staging/prod each have their own project and set of variables; deploy migrations and the app in order, staging before prod. Avoid sharing a DB across environments.
  • Don't log secrets: URLs can be logged (usually not sensitive); keys, passwords, and tokens must never be printed to logs or responses. CI and pre-commit check for key-leak patterns.
  • Deploy checklist: Having clear steps (migration → deploy app → verify) reduces human error and makes mental rollback easier (you know how far you've gotten).

Conclusion

The 10-post series ends here: from the Supabase overview, setup, schema, migrations, auth, Prisma, RLS, API, Realtime/Edge through to deploy and CI/CD. Apply environment separation, secure secrets, and a deploy checklist for stable operations.

Back to the start of the series: 00 — Series introduction

Further reading: Supabase — Self-hosting, Branching (if used).

LDK

Le Duy Khuong

AI Transformation & Digital Strategy. Writing about agentic systems, engineering leadership, and building in public.