Deployment
Runbook for shipping code to production and rolling it back when something goes wrong. Audience: developers and on-call engineers.
Architecture at a glance
- Frontend — Static SPA built with Vite, deployed to Cloudflare Pages. Auto-deploys on push to
main. Every pull request gets a preview URL. - Backend — Supabase managed Postgres + Auth + Storage, plus Edge Functions (Deno) deployed with the
supabaseCLI. - Release orchestration —
semantic-releaseonmainreads Conventional Commit messages, bumps the version, tags, and writesCHANGELOG.md. See.releaserc.jsonand.github/workflows/release.yml.
Branch and release flow
- Cut a feature branch:
feat/<scope>,fix/<scope>, orchore/<scope>(seeCLAUDE.md). - Open a PR into
main. Cloudflare Pages builds a preview URL; CI runs lint, tests, build, bundle-budget check, and Playwright e2e. - Merge with Squash and merge. Commit subject must follow Conventional Commits so
semantic-releasecan classify it:feat:→ minor version bumpfix:/perf:/revert:/refactor:→ patch- Footer
BREAKING CHANGE:→ major docs:/chore:/ci:/style:/test:→ no release
- After the squash lands on
main:- CI (
.github/workflows/ci.yml) runs again onmain. - If CI succeeds,
release.ymltriggers viaworkflow_run, runsnpx semantic-release, pushes avX.Y.Ztag, updatesCHANGELOG.md, and creates a GitHub Release. - Cloudflare Pages notices the new commit on
mainand publishes the build to production.
- CI (
Tip
Delete merged branches (local + remote) after squash. Keeps the branch list clean and avoids stale preview deploys sitting around.
Frontend build
npm run build runs vite build. The relevant configuration is in vite.config.ts:
- Output:
dist/(entry, route chunks, vendor chunks, all gzip + brotli pre-compressed). - Manual vendor splitting (React, Radix, charts, Supabase, forms, dates, xlsx, Sentry) — see
rollupOptions.output.manualChunks. - PWA service worker emitted via
vite-plugin-pwa(registerType: "prompt",injectManifest).
Required build-time env vars (CI sets these from GitHub secrets):
VITE_SUPABASE_URLVITE_SUPABASE_PUBLISHABLE_KEYVITE_SUPABASE_PROJECT_IDVITE_TURNSTILE_SITE_KEYVITE_GOOGLE_CLIENT_IDVITE_GOOGLE_DRIVE_FOLDER_IDVITE_SENTRY_DSN
The same list must be configured in Cloudflare Pages → Project → Settings → Environment variables for Production (and Preview if you want previews to talk to real Supabase).
Triggering a manual deploy
Frontend (Cloudflare Pages)
Auto-deploy usually works. When it stalls:
- Open Cloudflare dashboard → Workers & Pages → your project.
- Click Deployments → the failed build → Retry deployment.
- If the failure was a transient network/install issue, this is enough.
- If a secret or build command changed, edit it under Settings → Environment variables / Settings → Builds & deployments and retry.
To force a fresh build without a new commit:
# push an empty commit to main (only if you have the access)
git commit --allow-empty -m "chore: re-trigger deploy"
git push origin main
Edge Functions (Supabase)
Edge functions are not deployed by CI. Deploy manually from a local checkout with the Supabase CLI linked to the project (yzpfwdxpwalmfuodkxni per supabase/config.toml).
# one-time link
supabase link --project-ref yzpfwdxpwalmfuodkxni
# deploy a single function
supabase functions deploy razorpay-webhook
# deploy all functions in supabase/functions/
supabase functions deploy --all # <!-- TODO: verify flag spelling against installed CLI version -->
Warning
Some functions in supabase/config.toml have verify_jwt = false (e.g. razorpay-webhook, push-send, whatsapp-webhook) because they authenticate the request body themselves (HMAC signature or internal bearer token). If you add a new function and forget the verify_jwt stanza, the gateway will 401 legitimate webhook calls.
Rolling back
Code-level rollback (preferred)
The cleanest rollback is a new commit that reverts the bad change.
- Identify the bad commit on
main: - Revert it:
semantic-releaseclassifiesrevert:as a patch release, cuts a new version, and Cloudflare Pages republishes. Within a few minutes production is back on the previous behaviour.
Cloudflare Pages rollback (fast path for frontend-only regressions)
Use when a revert would take too long (e.g. outage mid-incident).
- Cloudflare dashboard → Workers & Pages → project → Deployments.
- Find the last known-good production deployment.
- Click the
...menu → Rollback to this deployment. - Confirm. Production now serves that build.
- Still cut the revert commit afterwards so the repo matches reality.
Danger
Cloudflare rollback only swaps the frontend. If the bad commit also shipped a database migration or Edge Function change, those are still live. You must additionally revert those — see database-migrations.md and the Edge Functions section above.
Edge Function rollback
supabase functions deploy overwrites in place; there is no built-in "previous version" button. To roll back, check out the previous commit and redeploy:
git checkout <previous-good-sha> -- supabase/functions/<function-name>
supabase functions deploy <function-name>
# then restore main and commit the revert properly
Database rollback
See database-migrations.md. Short version: write a new forward migration that undoes the bad change. Do not edit or delete already-applied migration files.
Secrets and environment variables
Canonical list of provider secrets lives in docs/secrets-setup.md. Quick map of where each class of secret belongs:
| Secret class | Stored in | Example |
|---|---|---|
| Public / frontend build-time | GitHub Actions secrets and Cloudflare Pages env vars | VITE_SUPABASE_URL, VITE_SENTRY_DSN |
| Edge Function runtime | Supabase project secrets (supabase secrets set) |
RESEND_API_KEY, RAZORPAY_WEBHOOK_SECRET, WHATSAPP_ACCESS_TOKEN |
| Backup workflow | GitHub Actions secrets | SUPABASE_DB_URL, AWS_* |
Danger
Never commit secrets to the repo, even in env.example. env.example holds only public keys (anon key, Turnstile site key, Google client ID) and placeholder strings.
Monitoring a deploy
- Watch CI status:
gh run watchor Actions tab on GitHub. - Once green, watch the Cloudflare Pages Deployments tab for the new build.
- Hit the production URL, open devtools Console, confirm no CSP violations and no Sentry errors spiking.
- For backend changes: check Supabase dashboard → Edge Functions → Logs and Database → Logs for the first few minutes after deploy.