Deployment
How Hatch is deployed in production: Vercel (web) + Railway (api) + Supabase (Postgres) + BNB Chain (contracts).
Also read: Vercel deploy runbook for the hands-on playbook.
Platform map
| Component | Platform | Trigger |
|---|---|---|
apps/web |
Vercel | main push → production; PRs → preview |
apps/api |
Railway (planned) | main push |
| Postgres | Supabase | managed, auto-scaling |
| Attester wallet | Hot wallet on BNB | manual rotation |
| Contracts | BNB testnet today, mainnet after C.7 | forge script DeployAll |
| DNS + WAF | Cloudflare (planned) | manual |
Web — Vercel
Production URL
gohatch.fun(once domain is connected)- Fallback:
hatch-*.vercel.app
Env vars (Vercel dashboard → Project Settings → Environment Variables)
| Name | Scope | Purpose |
|---|---|---|
HATCH_API_BASE_URL |
Production, Preview | e.g. https://api.gohatch.fun |
ADMIN_TOKEN |
Production | Admin cookie gate |
NEXT_PUBLIC_ORIGIN |
All | https://gohatch.fun |
VERCEL_GIT_COMMIT_SHA |
(auto) | Injected by Vercel — used by /admin/status |
Deploy Protection
Currently SSO-gated. Disable SSO for the public site to return 200 instead of 401. Settings → Deployment Protection → disable.
Preview URLs
Every PR gets a hatch-<slug>-<hash>.vercel.app URL. Preview deployments
use the Preview env; set HATCH_API_BASE_URL to staging if you want
previews isolated.
Promote to production
Vercel dashboard → pick a green deployment → "Promote to production". No code change needed. Rollback is the same UI flipping to an older green.
Build command
pnpm --filter @hatch/web build
Uses Turborepo — the web build depends on @hatch/ui, @hatch/shared,
and implicitly @hatch/sdk (types only).
API — Railway
Production URL
api.gohatch.fun(once DNS is set)
Env vars
| Name | Purpose |
|---|---|
DATABASE_URL |
Supabase connection string (use pooler URL) |
ANTHROPIC_API_KEY |
Claude access for scoring |
ANTHROPIC_MODEL |
Pin (e.g. claude-sonnet-4-5-20250929) |
PORT |
Railway provides |
BSC_RPC_URL |
For attestation writes |
BSC_CHAIN_ID |
56 mainnet, 97 testnet |
HATCH_ATTEST_ADDRESS |
Deployed HatchAttest contract |
ATTESTER_PRIVATE_KEY |
32-byte hex — Railway secret |
WEBHOOK_ENCRYPTION_KEY |
32-byte hex — AES-256-GCM key |
ADMIN_TOKEN |
Admin bearer — must match web side |
ENROLLMENT_DOMAIN |
gohatch.fun — part of canonical signature string |
BITQUERY_API_KEY |
(pending) |
GOPLUS_API_KEY |
(pending) |
GIT_COMMIT_SHA |
Railway injects via build variable |
Deploy flow
- Push to
main. - Railway detects the push, runs
pnpm install && pnpm --filter @hatch/api build. - Starts the API with
pnpm --filter @hatch/api start. - Health check
/healthmust 200 within 30s or deploy is aborted.
Rollback
Railway dashboard → previous deployment → "Redeploy". Takes ~90 seconds.
Scaling
- Vertical: upgrade plan in Railway dashboard.
- Horizontal: enable multiple replicas; ensure DB connection pool
(
pg.Pool) doesn't saturate Supabase's connection limit.
Postgres — Supabase
Pool URLs
Use the pooler (pgBouncer) URL, not the direct URL:
postgres://postgres:<pw>@db.<project>.supabase.co:6543/postgres
Pooler handles the connection count spikes during traffic bursts.
Migrations
Deployed ahead of app code:
# Locally, before merging the PR:
pnpm --filter @hatch/api db:generate # reviews SQL diff
git commit -m "db(…): new migration …"
# In production, after the PR merges:
pnpm --filter @hatch/api db:migrate # one-off from Railway console
See DEV-GUIDE.md — Database for the migration rules.
Backups
Supabase's daily backups (Pro plan). Retention: 7 days. Manual snapshot before destructive migrations.
Contracts — BNB Chain
Testnet (today)
cd packages/contracts
forge script script/DeployAll.s.sol \
--rpc-url $BSC_TESTNET_RPC_URL \
--private-key $DEPLOYER_PRIVATE_KEY \
--broadcast --verify \
--etherscan-api-key $BSCSCAN_API_KEY
Mainnet (gated on Sprint C.7)
Same script, different RPC. Requires:
- ✅ C.6 audit complete (OZ / Hacken / ToB)
- ✅ I.5 legal opinion
- ✅ Multisig deployer (not EOA)
- ✅
DeployAlldry-run simulated on testnet with production parameters
Post-deploy:
HatchRegistry.setAttester(<hot wallet>).- Rotate attester key immediately if deploy log touched it.
- Update
HATCH_ATTEST_ADDRESSon Railway.
DNS + CDN — Cloudflare (planned)
gohatch.fun→ Vercel (proxied or CNAME).api.gohatch.fun→ Railway.- Cloudflare WAF: rate-limit beyond API limits; block obvious scraper UAs.
- Redirect
www.gohatch.fun→gohatch.fun.
Canary + rollback
Web
- Deploy via Vercel. First ~5 mins go to ~10% of traffic via Vercel's built-in canary on the production domain. No manual steps.
- Watch Sentry errors +
/api/healthfrom a preview URL. - If regression detected → Vercel dashboard → promote previous green.
API
- Railway doesn't canary natively. Instead, deploy to a staging Railway service first.
- Run smoke tests against staging URL.
- Promote to prod by switching the
HATCH_API_BASE_URLenv on Vercel. - Rollback = flip
HATCH_API_BASE_URLback to the previous service URL.
Monitoring
- Uptime: UptimeRobot or Better Stack pinging
/healthevery 60s. - Errors: Sentry on both web + api (planned — L.6).
- Logs: pino JSON → Railway logs (+ optional Logtail/Grafana Loki).
- Metrics: OpenTelemetry OTLP → (TBD — Grafana Cloud or New Relic).
- Status page:
/admin/status(internal) +/transparency(public).
Cost envelope (target)
| Platform | Tier | Monthly cost (est.) |
|---|---|---|
| Vercel Pro | 1 project | $20 |
| Railway Team | api instance | $20–40 |
| Supabase Pro | 8GB DB | $25 |
| Anthropic | Claude Sonnet | ~$0.007 × 1000 scores = $7 |
| BNB gas | Attestation writes | <$10 |
| Total | ~$80–100/month |
Before any revenue (0.5% LP fee post-graduation). Operating on personal funds until mainnet.