Deployment
Supabase projects
Section titled “Supabase projects”| Project | Used by |
|---|---|
movement-mapping-dev |
Every developer’s local app, the dev app on Render, and PR previews |
movement-mapping-prod |
The production app |
Both are in the EU. Migrations reach them as described in Migrations.
Project settings outside migrations
Section titled “Project settings outside migrations”Some settings live at the Supabase project level, where a migration can’t reach them:
- PostgREST’s exposed schemas. A new project exposes only
public, soappandnetworkqueries fail until they’re added. - The Auth
site_url. Auth emails link to it. A new project useshttp://localhost:3000. - The Auth redirect allow-list. Until
/auth/confirmand/auth/callbackare on it, sign-in links are rejected. - Custom SMTP and email templates. Without custom SMTP, Supabase won’t let you edit
the templates. The default templates use a link that
/auth/confirmcan’t handle, so confirmation and magic-link sign-in fail.
scripts/supa-configure-project.sh sets all of these. It runs automatically on a
project’s first migration (see First-run setup). For
dev and prod, run it once by hand when the project is created:
SUPABASE_PROJECT_REF=<ref> SITE_URL=<public app URL> \ RESEND_API_KEY=<key> SMTP_SENDER_EMAIL=<address> \ ./scripts/supa-configure-project.sh- Run it from a checkout with no
.env, or with.envmoved aside. The script loads.envif it exists, and that overrides the values on the command line. - Without
RESEND_API_KEYandSMTP_SENDER_EMAIL, it skips the email step and prints the template text to paste in by hand (Dashboard → Authentication → Email Templates).
Render services
Section titled “Render services”render.yaml defines everything, in Frankfurt:
| Service | What | Deploys |
|---|---|---|
network-mapping-app |
The production app, against prod Supabase | Only from a published release (autoDeployTrigger: "off") |
network-mapping-app-dev |
The same app against dev Supabase, on the free plan. It sleeps when idle; the first request takes about a minute. | Every push to main, plus a preview per PR |
network-mapping-docs |
This site, a static build of apps/docs |
Every push to main |
network-mapping-purge |
A daily cron that calls POST /api/maintenance/purge on prod |
On schedule (03:00) |
Both app services use GET /api/health as their health check. It returns { "ok": true }
and needs no session or setup.
Secrets are set in Render’s dashboard, not committed. render.yaml marks them
sync: false. The Resend variables aren’t declared in render.yaml at all, so set them
on each app service by hand. See Environment variables.
Docs deploy from main while the app deploys from releases. So the published docs can
describe features that aren’t in the released app yet.
Releases
Section titled “Releases”pnpm release # work out the version from conventional commitspnpm release minor # or major / patchpnpm release v2.0.0 # or an exact versionpnpm release --dry-run # print everything, create nothingThe script needs an authenticated gh CLI. It creates a draft release and stops.
The draft lists every pending migration, because publishing applies them to production.
It doesn’t create a tag or push anything; GitHub creates the tag on publish. To abandon
a draft, delete it with gh release delete <tag>.
Publishing the release runs promote-prod in .github/workflows/db.yml:
- It applies pending migrations to
movement-mapping-prod. - Only if that succeeds, it calls the app’s Render deploy hook, pinned to the
release’s commit with
&ref=<sha>.
The job refuses to deploy a commit that isn’t on main, skips pre-releases, and queues
rather than cancels when two runs overlap. If a promotion fails part-way, re-run the
job. supabase db push skips migrations already applied, and the hook fires again.
A deploy-hook deploy uses the build command and environment Render already has. A
release that changes render.yaml needs the blueprint synced in Render as well.
PR previews
Section titled “PR previews”Each pull request gets a preview of the app. Render creates it when the PR opens, deletes it when the PR closes, and posts its URL on the PR.
- Previews come from
network-mapping-app-dev, so they use the dev database and dev secrets. Never preview from the production service: a preview copies its base service’s environment. - Previews run on the
starterplan so they don’t sleep during review. - A preview runs against whatever schema dev has. That includes migrations someone has
pushed with
scripts/supa, but not the PR’s own, unless they’ve been pushed. - Invitation links use the preview’s own URL (
RENDER_EXTERNAL_URL), since a preview inherits the dev service’sSITE_URL. Emails Supabase sends itself, such as sign-up confirmation, still use the project’ssite_url.
One-time setup:
- Sync the blueprint in Render. Set
network-mapping-app-dev’s secrets to the dev values:VITE_SUPABASE_URL,VITE_SUPABASE_PUBLISHABLE_KEY,SUPABASE_SECRET_KEY,VITE_DOCS_URL, andSITE_URLas the dev service’s URL. Check the first preview’s environment before trusting it. - In the dev Supabase project, add the dev service’s URL and a preview wildcard (such
as
https://network-mapping-app-dev*.onrender.com/**) to Authentication → URL Configuration → Redirect URLs.
.github/workflows/ci.ymlruns on pull requests and pushes tomain. It does a frozen-lockfile install, thenbiome check, the typecheck, andpnpm build(which builds every workspace, includingpackages/). It runs the Socket.dev scan whenSOCKET_SECURITY_API_KEYis set, without failing on its findings. It doesn’t run tests..github/workflows/db.ymlmigrates dev on every push tomain(apply-dev, in the GitHubdevenvironment), and runspromote-prodon release (in theprodenvironment)..github/workflows/supabase-branch-cleanup.ymlruns when a git branch is deleted, and tries to delete a matching Supabase branch. With branching disabled, there’s nothing for it to delete.