Deployment
The public, read-only copy: Vercel in front, Catalyst AppSail behind, a masked snapshot.
The real pipeline runs on your laptop. The deployed site is a read-only window onto a copy of what it found: the dashboard on Vercel, the API on Zoho Catalyst AppSail, and one snapshot of the database with every email address masked inside the file. Nobody there can send, spend or write anything.
The pieces#
| Piece | Where | Notes |
|---|---|---|
| Dashboard (and these docs) | Vercel | Every docs page is pre-rendered: it is served as finished HTML and never waits for the API. |
| API proxy | /zapi/* | Same-origin rewrite on the Vercel domain to the backend (next.config.ts) — no CORS to configure. Reads the backend marks cacheable are kept at the edge for 10 minutes, then served stale while it refreshes. |
| API | Catalyst AppSail · Python 3.12 · 1024 MB | The Development environment is the live backend; ZONULY_PUBLIC=1 refuses every write and every private read. |
| Data | demo.db.gz | A stripped, masked snapshot, committed to the repository and unpacked into /tmp when the API starts. |
The snapshot#
scripts/make_demo_db.py builds demo.db.gz from your working database. Because the file sits in a public repository, hiding addresses in the API was not enough — so the file itself is cleaned:
| What | How |
|---|---|
| Every email address | Replaced, in every text column of every table (the knowledge graph’s search index included), by a stand-in on the same domain: first letter + seven characters of a hash. The site looks the same; no address in the file reaches a real inbox. |
| Emptied | Cached pages, model spend, inbound replies, your Upskill notes and proof links, real interview outcomes, the mixer’s spend ledger, and the scrape audit trail (extraction_write / extraction_reject). |
| On screen | The public API also shows addresses masked, as p•••@company.com. |
python scripts/make_demo_db.py # -> demo.db.gz
git add demo.db.gz && git commit -m "Refresh the snapshot" && git pushHow a push becomes the live site#
- Smoke test the snapshotOn every push to
main, thesnapshotworkflow unpacksdemo.db.gzand runsscripts/smoke.pyagainst it — a snapshot older than the code's schema fails here, not in production. - Build the backend
scripts/build_catalyst.pyassemblesdeploy/catalyst/appsail/build/: the code, config, lists and snapshot, with every dependency fetched as a Linux wheel. Never shipped:.env,secrets/,jobhunter.db,profile/, backups — and not Playwright or the Google API client, which a read-only instance cannot reach. - Deploy and checkThe workflow deploys to Catalyst Development with the
CATALYST_TOKENrepository secret, waits for/api/health, and runs the smoke test against the live URL. - The dashboardThe workflow ships only the backend. The dashboard is deployed to Vercel separately; on Vercel its build sets the API base to the
/zapiproxy.
A sleeping backend#
The Development instance sleeps about a minute after its last request. The first visitor after that waits while it wakes and unpacks the snapshot — measured at 7.5 s for /api/overviewon 23 Oct 2026, and up to half a minute right after a deploy. The dashboard retries a read for about 35 seconds before saying it can't reach the API; the docs never wait at all, and the front page's live figures fill in when they arrive.
Where public mode switches things off, see the Public mode note in the Dashboard tour and the conventions in the HTTP API.