architecture
FIG. 04 · INFRA in production

One template, a fleet of sites

2 min read
astrogithub actionsterraformamplifycloudflare dnssupabase rls
template repo4 design skins create-clientrepo · secrets · tier client repo ×ntemplate sync prs amplify + acmper-client terraform dns cutovercert-gated shared supabaserls · per-tenant jwt

FIG. 04: fleet provisioning platform · infra

astro github actions terraform amplify cloudflare dns supabase rls

Tenancy lives in the database, not the application code. One template repository provisions every client site (repo creation, secret and variable copying, tier-adjusted feature flags, infrastructure, DNS), and every site that comes out of it shares one Postgres project instead of getting its own. If a request reaches the database with the wrong tenant claim in its token, row-level security refuses it before the query runs, regardless of what the application layer thinks it’s allowed to do.

The design decision

I could have given each client its own database, its own Supabase project, its own migration history to keep in sync. Instead every client site ships with a per-tenant signed JWT baked into its environment at provisioning time, and a single RLS policy on each table scopes reads and writes to the tenant claim inside that token. The anonymous client is already tenant-scoped the moment it connects. There’s no header to forget and no application-level filter that a future change could quietly drop. Four design skins currently share this same data layer and provisioning path.

Provisioning as a pipeline, not a runbook

A create-client workflow does the repo creation, copies secrets and variables from the template, patches the tier-specific config, and hands off to infrastructure setup: Amplify and ACM through Terraform, one client app per site. DNS is deliberately the last step: a separate cutover workflow only creates the Cloudflare CNAME after the ACM certificate has verified, which rules out the half-provisioned custom domain that a single combined step would risk.

Keeping the fleet in sync

The harder problem isn’t provisioning a site once. It’s changing the template after ten sites already exist from it. Every push to the template’s main branch diffs itself against each live client and opens a pull request against that client’s branch rather than pushing directly, so a template change is reviewable per site before it lands, and a client-owned file is never silently overwritten by the sync.

registered as FIG. 04 · service registry