Adds .woodpecker.yml (lint + typecheck on every push, auto-deploy to a shared staging environment on every push, manual Deploy-button promotion to production on main) and env.staging blocks in both wrangler configs so staging gets its own Workers (diagrav-app-staging/diagrav-site-staging at staging-app.diagrav.com/staging.diagrav.com) rather than sharing anything with production. Staging also got its own fully separate Supabase Cloud project (own database, own Auth config reusing the same Google OAuth client with an extra redirect URI, own Resend-backed SMTP) — migrated, seeded with the public catalog, and verified end-to-end with a real sign-in through the deployed app before wiring any of this into CI. Updates deployment-plan.md's CI/CD section to match what actually got built, superseding the earlier manual-pushbutton-for-both-environments version of the plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
10 KiB
Deployment & CI/CD planning notes
Started as discussion-only notes; hosting and CI/CD are now both live (see their sections below for what's actually implemented vs. still open). Written so a different Claude Code conversation on this repo can pick up where this one left off without re-deriving it.
Hosting shape — implemented
The app is a static frontend (Vite build, no server of its own) + Supabase backend (Postgres, Auth, RLS, one Edge Function). organized-ideas.md already commits to this being provider-agnostic (Supabase Cloud, self-hosted Supabase Docker stack, or export the Postgres DB elsewhere — no app code changes needed either way).
To go live:
- Backend: create a Supabase Cloud project,
supabase link+supabase db pushto apply the 15 existing migrations,supabase functions deploy admin-user-action. Set real Google OAuth credentials for the production redirect URL — right nowsupabase/config.tomlandvite.config.tsare hardcoded to127.0.0.1withskip_nonce_check = truefor local dev; this needs a production config pass. Needs a real SMTP provider for auth emails (Resend suggested in organized-ideas.md as the default) since Inbucket (local fake mail catcher) doesn't exist in the cloud. - Frontend:
npm run build→ staticdist/, deploy to any static host (Vercel/Netlify/Cloudflare Pages, or self-hosted). SetVITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEYas build-time env vars pointing at the real Supabase project (Vite bakes these in at build time — not runtime-configurable without a refactor).
Marketing site (site/)
A separate, informational "what is this thing" page lives in site/ at the
repo root — plain hand-written HTML/CSS (index.html + style.css +
images/), no build step, not part of the Vite app or its src/ tree at
all. Screenshots under images/ are real captures from the app (a demo
DAWless rig wired up for the purpose), referenced with relative paths, so
the folder is fully self-contained.
Deploys the same way the app's frontend would, just simpler — no env vars to bake in, no build command, just serve the static files as-is. Same free-tier host options apply (Vercel/Netlify/Cloudflare Pages, or self-hosted).
Domain purchased: diagrav.com. The app was renamed from "AV Planner" to
Diagrav to match. The site's nav ("Log in" / "Sign up") and its closing
CTA ("Sign up free") now link to https://app.diagrav.com/ — so the
subdomain-split option below is the working assumption baked into the site
as of now, even though neither diagrav.com nor app.diagrav.com is
actually deployed anywhere yet. Those links will 404 until the app is
actually live at that subdomain; treat this as a leaning, not a final
decision — swapping to the path-based option only means changing the few
hrefs in site/index.html, nothing structural.
- Subdomain split (current assumption): bare
diagrav.comfor the marketing site,app.diagrav.comfor the actual tool — cleanest separation, two hosting targets instead of one. - Same domain, different path (marketing at
/, app at/appor similar) — one hosting target, but means the static host needs to route/tosite/and everything else to the Vitedist/output, which the simple "just deploydist/" flow above doesn't currently account for.
Whichever is chosen, this needs its own line in the CI/CD plan below (currently only the app's frontend/backend deploy steps are scoped) — it's a third deployable alongside "frontend" and "backend," not an afterthought folded into the frontend step.
Cost / free-tier findings
Provider: Supabase, since the app already codes directly against its
SDK/Auth/RLS model (src/data/Supabase*.ts) — switching providers would mean
rewriting those repositories.
Current Supabase Free plan limits (confirmed against supabase.com/pricing, not from training data — re-verify if this doc is read much later):
| Resource | Free limit |
|---|---|
| Database size | 500 MB |
| Egress | 10 GB/mo (5 GB cached + 5 GB uncached) |
| Monthly active users | 50,000 |
| Edge Function invocations | 500,000/mo |
| File storage | 1 GB |
| Realtime concurrent connections | 200 |
| Inactivity | paused after 1 week idle, max 2 active projects |
Conclusion: this app is storage-bound, not bandwidth-bound. File storage,
realtime, and edge function limits are all non-issues (no file uploads, no
supabase.channel(...) usage anywhere in src/, and the one edge function
is admin-only/rare). Egress is generous relative to what this app transfers
per session. The one limit likely to actually bite is database size
(500 MB) — driven less by user-count/traffic than by long-term accumulation
in diagrams + diagram_snapshots.
Specific risk found: SupabaseDiagramRepository.ts (maybeWriteSnapshot,
~line 70-93) writes a new version-history snapshot row every 5 minutes of
active editing, with no retention/expiry — they accumulate indefinitely.
A user who edits diagrams regularly for months will grow the DB more than a
burst of new signups would. Suggested follow-up (not yet done): add
snapshot retention — e.g. keep the last N snapshots per diagram, or prune
anything older than ~90 days. This is the highest-leverage lever for keeping
DB size (and therefore cost/free-tier headroom) under control.
Rough scale ballpark discussed: solo/small-team use won't come close to any limit for years. Somewhere in the ~300–1,500 regular-active-user range with several diagrams each is roughly where the 500MB DB cap would start to bite, well before the 10GB egress cap or 50k MAU cap would matter. Treat this as a rough estimate, not a measured number — worth checking real diagram JSON size in Studio once there's real usage.
Estimated cost: $0/mo to start (Supabase Free + Vercel/Netlify/Cloudflare Pages free tier + Resend free tier for auth email), $25/mo once you want Supabase Pro (no project auto-pause, higher limits across the board).
CI/CD: Woodpecker, auto-staging / manual-prod — implemented
Repo is self-hosted on Gitea (git.halfbinary.net/aarbit/av-planner) with
Woodpecker CI already in use elsewhere. .woodpecker.yml now exists at the
repo root. The flow below supersedes an earlier version of this section that
proposed manual pushbutton deploys for both staging and production —
revisited and simplified to auto-deploy staging instead.
Actual flow: any push (any branch) runs lint + typecheck, then
auto-deploys to a shared staging environment — no button needed. Production
is never touched automatically; promoting to it is a manual Deploy
button click (Woodpecker's built-in deploy event) on a main-branch
pipeline run, typing production as the deploy target
($CI_PIPELINE_DEPLOY_TARGET). Requires "Allow deployments" enabled in
the repo's Woodpecker project settings.
Two fully separate environments exist, each with its own everything (no shared data or infra between them, or with production):
| Staging | Production | |
|---|---|---|
| Supabase project | onovxozixdxpdngxafxt ("Diagrav Staging") |
wmombshptajyrawdbroj ("Diagrav") |
| App Worker | diagrav-app-staging → staging-app.diagrav.com |
diagrav-app → app.diagrav.com |
| Site Worker | diagrav-site-staging → staging.diagrav.com |
diagrav-site → diagrav.com |
This uses both of Supabase Free's "2 active projects" slots — no headroom for a third project if one's ever wanted later (see cost findings above).
How each piece works:
- Wrangler:
wrangler.app.jsonc/wrangler.site.jsonceach gained anenv.stagingblock (ownname+ ownroutes) —assets.directoryisn't per-environment in Wrangler, which is fine, since it's rebuilt with differentVITE_*values before each deploy anyway (the "known wrinkle" below). - Known wrinkle (as anticipated): Vite bakes
VITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEYin at build time, sonpm run buildreruns once per target with that target's env vars set, rather than promoting one artifact between environments. - Migrations:
supabase db push --project-ref $REF --password $SUPABASE_DB_PASSWORDper target (no persistentsupabase link— every command that needs a project takes--project-refdirectly, which also meant this never touched the local repo's ownsupabase linkstate, still pointed at production for everyday local dev). - Google OAuth: staging reuses production's same OAuth client rather
than a new one — just one more authorized redirect URI
(
https://onovxozixdxpdngxafxt.supabase.co/auth/v1/callback) added to it in Google Cloud Console, since a client can authorize many redirect URIs. - SMTP: staging reuses the same Resend account/verified domain as
production, with a distinct
smtp_sender_name("Diagrav (Staging)") so the two are distinguishable in an inbox. - Staging catalog data: seeded once by applying
supabase/seed.sqldirectly against the new project (not part of the pipeline — a one-time setup step, same as production's original seeding).
Secrets in Woodpecker (repo Settings → Secrets): cloudflare_api_token,
cloudflare_account_id, supabase_access_token (shared); per-target
{staging,prod}_supabase_url, {staging,prod}_supabase_anon_key,
{staging,prod}_supabase_project_ref, {staging,prod}_supabase_db_password.
Outstanding: the production supabase_db_password secret — Supabase
doesn't expose an existing project's DB password via the Management API, so
this needs either the password from wherever it was originally saved, or a
deliberate decision to rotate it (a live production credential change,
not something to do silently). The staging path has been fully verified
end-to-end (migrations, seed, Auth, a real sign-in through the deployed
app); production's manual deploy path has not been exercised yet — no
reason to touch real prod while proving the pipeline out.
Open decisions (not yet made)
- Frontend host and the marketing site's placement are both decided and
live, superseding the two bullets that used to be here: Cloudflare
Workers (assets-only, no server code) for both, subdomain split
(
diagrav.commarketing site +app.diagrav.comapp), matching the links already insite/index.html. - Whether/when to add diagram-snapshot retention (the storage-risk item above) — not done yet, just identified.
- The production
supabase_db_passwordgap noted above.