Environments : staging + production
Three environments, mapped to branches:
| Environment | Branch | Web (Vercel) | API + crons (Render) | Database (Supabase) |
|---|---|---|---|---|
| Local dev | any | pnpm dev | pnpm dev | local supabase start |
| Staging | develop | Preview deploy (stable branch domain) | monark-api-staging + monark-cron-*-staging | separate staging project |
| Production | main | Production deploy | monark-api + monark-cron-* | production project |
A push to develop redeploys staging ; a push to main redeploys
production. Both are native git deploys ; Render autoDeploy and Vercel's git
integration each watch their branch ; so there is no separate deploy workflow to
run. Deploys fire on push in parallel with CI (see ci.md); staging
is where you catch what the gate can't.
The golden rule: staging and production never share a database. Staging
points at its own Supabase project so you can seed, migrate, and break it without
touching production data. Every sync: false env var in
render.yaml holds a separate value per environment.
This guide covers the two environments of one instance. To run many isolated single-tenant instances ; one per business, each with its own database + domain via containers + Cloudflare ; see multi-instance.md.
Promotion flow
feature branch → PR → merge to `develop` → auto-deploys STAGING
→ verify on staging
`develop` → PR → merge to `main` → auto-deploys PRODUCTION
The first production deploy is the one-time deploy-checklist.md walkthrough. This document is the staging half plus the branch→env wiring; do the production setup first, then repeat the DB/Render/Vercel steps for staging.
Render (api + crons)
render.yaml is one Blueprint that defines both
environments: the production services on branch: main and mirror
*-staging services on branch: develop. The build / migrate / start / cron
commands are identical (shared via YAML anchors); only the branch, the service
names, and the sync: false values differ.
Setup:
- Create the Blueprint once (New → Blueprint → this repo). Render provisions
all six services + both env-var groups and prompts for every
sync: falsevalue. - Fill the production api's env from the real production Supabase project
(as in the deploy checklist), and
monark-cron-sharedwith the productionCRON_SECRET+API_URL. - Fill the
monark-api-stagingenv from the staging Supabase project, andmonark-cron-shared-stagingwith a distinct stagingCRON_SECRET+ the staging api URL. SENTRY_ENVIRONMENTis pre-set (production/staging) so Sentry separates the two automatically; setSENTRY_DSNon both (same or separate Sentry projects). See observability.md.
Each api service runs its own prisma migrate deploy in preDeployCommand, so a
push to develop migrates the staging DB and a push to main migrates
production : independently. A migration that fails on staging aborts the staging
deploy and never reaches production.
Staging can run on a smaller Render plan than production ; drop plan: on the
*-staging services if you want to save cost (a free plan spins down when idle,
which is usually fine for staging).
Vercel (web)
Vercel maps branches to environments through the dashboard, not vercel.json:
- Project → Settings → Git → Production Branch =
main. Every push tomainis a Production deploy; every other branch (includingdevelop) is a Preview deploy automatically. - Give
developa stable staging URL. Vercel already assigns a durable per-branch alias (<project>-git-develop-<team>.vercel.app). For a nicer host, add a domain (e.g.staging.yourdomain.com) under Settings → Domains and assign it to thedevelopbranch. - Scope env vars by environment. Under Settings → Environment Variables,
set each
NEXT_PUBLIC_*for Production (→ prod api / prod Supabase) and a separate value for Preview (→ staging api / staging Supabase):NEXT_PUBLIC_API_URL,NEXT_PUBLIC_APP_URL,NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, plus the optionalNEXT_PUBLIC_SENTRY_DSN/NEXT_PUBLIC_BRANDING_*. Preview values point the staging web at the staging api + staging Supabase.
The vercel.json ignoreCommand skips a build
when a push touched nothing under services/web or the shared packages ; that
applies to both environments.
Note on PR previews. Because every non-production branch is a Vercel Preview, each PR also gets its own ephemeral preview URL ; but those share the Preview env vars, so they talk to the staging api + Supabase. Treat PR previews as staging-data clients, not isolated sandboxes.
Supabase (database + auth)
Create a second Supabase project for staging. It needs the same setup as production (deploy-checklist.md Phase 0.2):
- Copy its
URL/ publishable / secret keys into the staging Render api env and the Vercel Preview env vars. - Authentication → URL Configuration → set the Site URL + redirect URLs
to the staging web URL (the
developVercel domain), independently of production. - Migrations reach it through the staging api's
preDeployCommandon eachdevelopdeploy : you don't run them by hand.
(Supabase's own preview branching is an alternative for ephemeral per-PR
databases; a dedicated staging project is simpler for a single always-on staging
environment and is what render.yaml assumes.)
Sentry
SENTRY_ENVIRONMENT is production on the prod services and staging on the
staging ones (set in render.yaml), and the web reads
NEXT_PUBLIC_SENTRY_ENVIRONMENT per Vercel environment. Point both at the same
Sentry project and use the environment filter, or use separate projects ; either
way errors are attributed to the right environment. See
observability.md.
First-time staging checklist
- Create the staging Supabase project; copy its URL + keys.
- Sync the Render Blueprint (if not already) → fill
monark-api-staging+monark-cron-shared-stagingfrom the staging Supabase + a fresh stagingCRON_SECRET. Copy the staging api URL back intomonark-cron-shared-staging.API_URL. - In Vercel, set Production Branch =
main, add the staging domain todevelop, and fill the Preview env vars (staging api + staging Supabase). - Set the staging Supabase Auth URL config to the staging web URL.
- Push to
develop→ watch staging build in Render + Vercel → smoke-test the staging URL (the deploy-checklist.md Phase 4 steps).
Optional: gate deploys on CI
Native deploys fire on push regardless of CI. If you'd rather deploy only after
the verify gate is green, disable autoDeploy on the Render services + turn off
Vercel's automatic git deploys, and trigger each platform's deploy hook from a
GitHub Actions job keyed on workflow_run (the CI workflow) conclusion == success.
Not wired today : staging deploying on every push is the intended behaviour, so a
red build is visible on staging quickly.