Deployment
Ship Kit to production. The web app targets Vercel; the database targets Supabase. Mobile, extension, and desktop each have their own pipeline.
Web App (Vercel)
1. Create the Project
Import the repo at vercel.com/new:
- Root Directory:
apps/web - Vercel detects Next.js and Turborepo automatically - no
vercel.jsonneeded
Or via CLI:
npm i -g vercel
vercel login
vercel # from apps/web2. Environment Variables
Set in the Vercel dashboard (same keys as .env.example):
# Database — Supavisor transaction pooler URL (:6543)
POSTGRES_URL=postgresql://...
# Auth
BETTER_AUTH_SECRET=generate-with-openssl-rand--base64-32
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
# AI chat
AI_GATEWAY_API_KEY=...
# Avatar uploads (Supabase Storage)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=...The auth base URL derives from Vercel's built-in VERCEL_ENV / VERCEL_PROJECT_PRODUCTION_URL / VERCEL_URL - nothing to set. Adding the Supabase integration to the Vercel project populates the Postgres and Supabase vars for you.
3. Preview Deployments
The oAuthProxy plugin in packages/api/src/auth/auth.ts routes OAuth callbacks through the production URL, so GitHub login works on preview deployments without registering every preview domain.
4. Custom Domain
- Add your domain in the Vercel dashboard
- Update DNS as instructed
- SSL is automatic
- Update your GitHub OAuth app's callback URL
Database (Supabase)
-
Create a production project at supabase.com
-
Push the schema:
# .env.production.local at the repo root with the production POSTGRES_URL pnpm db:push-remotedrizzle-kit pushconnects directly (the config swaps :6543 for :5432 automatically). -
Create the avatars bucket - a public
avatarsbucket, no policies needed (writes go through/api/account/avatarwith the service-role key). Local dev gets this fromconfig.toml; hosted projects need it created in the dashboard.
RLS is already on for every table with no policies - deny by default. Authorization lives in tRPC; the server connection bypasses RLS as table owner.
Mobile (EAS)
Production builds need to know where the API lives:
// eas.json build profile env
{ "EXPO_PUBLIC_API_URL": "https://your-domain.com" }Dev builds derive the URL from Metro. Then follow the standard EAS Build flow.
Extension and Desktop
pnpm -F @repo/extension build # wxt build → .output/chrome-mv3
pnpm -F @repo/desktop build # electron-vite build; package with electron-builderUpload the extension to the Chrome Web Store; distribute desktop builds per electron-builder.yml.
CI/CD
Vercel deploys on push once the repo is connected - previews per PR, production on main. Schema pushes stay manual (pnpm db:push-remote) or go in a workflow step with POSTGRES_URL as a secret:
- name: Push database schema
env:
POSTGRES_URL: ${{ secrets.POSTGRES_URL }}
run: pnpm -F db drizzle:kit push --forceRun checks before merging:
pnpm lint && pnpm typecheck && pnpm test && pnpm buildHealth Checks
The app ships a liveness endpoint at /api/health (apps/web/src/app/api/health/route.ts). Point your uptime monitor at it:
curl https://your-domain.com/api/health
# {"status":"ok"}Rollback
vercel ls # List deployments
vercel rollback [deployment-url] # Roll back the web appdrizzle-kit push has no down migrations - take a backup before risky schema changes:
pg_dump $POSTGRES_URL > backup_$(date +%Y%m%d_%H%M%S).sqlSupabase paid plans also keep automated backups.
Production Checklist
BETTER_AUTH_SECRETset and unique per environment- Separate GitHub OAuth apps for dev and prod (callback:
https://your-domain.com/api/auth/callback/github) POSTGRES_URLuses the transaction pooler (:6543) - the client is configured for it- Avatars bucket exists in the production Supabase project (no policies needed)
- Uptime monitor on
/api/health
Next Steps
- Monitoring - Set up monitoring and observability
- Production checklist - Review production best practices