Files
av-planner/deployment-plan.md
T
aarbitandClaude Sonnet 5 0a7f2f7fee 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
2026-09-28 10:05:59 -05:00

9.7 KiB
Raw Blame History

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 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 hrefs 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.:

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:
    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.