Deploy to Cloudflare Workers and rebrand to Diagrav
Adds Workers + static-assets configs for the app (app.diagrav.com) and a separate marketing/landing site (diagrav.com), plus the deployment runbook used to stand up Supabase Cloud, Google OAuth, Resend SMTP, and Cloudflare DNS/custom domains. Renames the app from "AV Planner" to "Diagrav" to match the new domain. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# Deployment & CI/CD planning notes
|
||||
|
||||
Discussion notes only — nothing here has been implemented yet. Written so a
|
||||
different Claude Code conversation on this repo can pick up where this one
|
||||
left off without re-deriving it. Nothing below is committed to as final; it's
|
||||
a record of what was discussed and concluded, plus open decisions.
|
||||
|
||||
## Hosting shape (discussed, not yet 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](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:
|
||||
1. **Backend**: create a Supabase Cloud project, `supabase link` + `supabase db push`
|
||||
to apply the 15 existing migrations, `supabase functions deploy admin-user-action`.
|
||||
Set real Google OAuth credentials for the production redirect URL — right
|
||||
now `supabase/config.toml` and `vite.config.ts` are hardcoded to
|
||||
`127.0.0.1` with `skip_nonce_check = true` for 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.
|
||||
2. **Frontend**: `npm run build` → static `dist/`, deploy to any static host
|
||||
(Vercel/Netlify/Cloudflare Pages, or self-hosted). Set
|
||||
`VITE_SUPABASE_URL` / `VITE_SUPABASE_ANON_KEY` as 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
|
||||
`href`s in `site/index.html`, nothing structural.
|
||||
- **Subdomain split (current assumption)**: bare `diagrav.com` for the
|
||||
marketing site, `app.diagrav.com` for the actual tool — cleanest
|
||||
separation, two hosting targets instead of one.
|
||||
- **Same domain, different path** (marketing at `/`, app at `/app` or
|
||||
similar) — one hosting target, but means the static host needs to route
|
||||
`/` to `site/` and everything else to the Vite `dist/` output, which the
|
||||
simple "just deploy `dist/`" 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 plan: Gitea + Woodpecker, pushbutton staging/prod
|
||||
|
||||
Repo is self-hosted on Gitea (`git.halfbinary.net/aarbit/av-planner`) with
|
||||
Woodpecker CI already in use elsewhere. No `.woodpecker.yml` exists yet in
|
||||
this repo.
|
||||
|
||||
**Desired flow**: PR merges to `main` → pipeline runs lint/typecheck/build
|
||||
and stops → a human clicks a "Deploy" button to push to staging, and
|
||||
separately clicks it again to promote to production. Not fully automatic on
|
||||
merge — deploys to both environments are manual/pushbutton.
|
||||
|
||||
**Mechanism (confirmed against current Woodpecker docs)**: Woodpecker has a
|
||||
built-in **deploy event** for exactly this. Enable **"Allow deployments"** in
|
||||
the repo's Woodpecker project settings, and any successful pipeline run gets
|
||||
a **Deploy** button in the UI. Clicking it re-runs that pipeline with
|
||||
`event: deploy` and a `deploy_to` string you type (e.g. `staging` or
|
||||
`production`), exposed to steps as `$CI_PIPELINE_DEPLOY_TARGET`. Steps gate
|
||||
on it, e.g.:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: deploy-staging
|
||||
when:
|
||||
- event: deploy
|
||||
evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "staging"'
|
||||
- name: deploy-prod
|
||||
when:
|
||||
- event: deploy
|
||||
evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "production"'
|
||||
```
|
||||
|
||||
**What needs to exist**:
|
||||
1. **Two Supabase projects** — staging and prod. This exactly fills the free
|
||||
tier's "2 active projects" cap (see above) — no free-tier headroom left
|
||||
for a third project if one is ever wanted later.
|
||||
2. **Two frontend hosting targets** — two sites/deployments (SaaS host or
|
||||
self-hosted alongside Gitea/Woodpecker — not yet decided which).
|
||||
3. **Known wrinkle**: because Vite bakes `VITE_SUPABASE_URL`/`VITE_SUPABASE_ANON_KEY`
|
||||
in at build time, you can't build once and promote the identical artifact
|
||||
to both environments — each deploy target needs its own build with its
|
||||
own env vars baked in. Not hard, just means "deploy" reruns the build
|
||||
rather than reusing one artifact. A non-issue at this app's scale.
|
||||
4. **Migrations run per-target** via Supabase CLI, scripted per environment:
|
||||
```bash
|
||||
supabase link --project-ref $SUPABASE_PROJECT_REF
|
||||
supabase db push
|
||||
supabase functions deploy admin-user-action
|
||||
```
|
||||
5. **Secrets needed in Woodpecker** (scoped per target): `SUPABASE_ACCESS_TOKEN`,
|
||||
staging/prod `SUPABASE_PROJECT_REF`, staging/prod `VITE_SUPABASE_URL` /
|
||||
`VITE_SUPABASE_ANON_KEY`, plus whatever the frontend host needs (API
|
||||
token, or SSH key if self-hosted).
|
||||
6. **Google OAuth**: needs its own authorized redirect URI added in Google
|
||||
Cloud console for each of the staging and prod domains — one-time setup,
|
||||
easy to forget.
|
||||
|
||||
**Difficulty estimate discussed**: roughly a focused afternoon — nothing here
|
||||
fights the tooling (no containers to orchestrate, no server process to run).
|
||||
Breakdown: `.woodpecker.yml` (~1–2 hrs), second Supabase project + migration
|
||||
check (~30 min), frontend hosting target wiring (~30–60 min), secrets +
|
||||
"Allow deployments" toggle (~15 min), end-to-end test/debug (~1–2 hrs).
|
||||
|
||||
## Open decisions (not yet made)
|
||||
|
||||
- Which frontend host: SaaS (Vercel/Netlify/Cloudflare Pages) vs self-hosted
|
||||
alongside the existing Gitea/Woodpecker infra.
|
||||
- Where the marketing site (`site/`, see above) lives relative to the app —
|
||||
leaning subdomain (`diagrav.com` + `app.diagrav.com`, per the links already
|
||||
in `site/index.html`) over same-domain/different-path, but not finalized —
|
||||
and whether it gets its own line in the eventual `.woodpecker.yml` or
|
||||
piggybacks on the frontend deploy step.
|
||||
- Whether/when to add diagram-snapshot retention (the storage-risk item
|
||||
above) — not done yet, just identified.
|
||||
- Actual `.woodpecker.yml` has not been written yet — this doc captures the
|
||||
plan/shape only, per the user's request to keep this conversation
|
||||
discussion-only and not make repo changes here.
|
||||
Reference in New Issue
Block a user