Documentation
KeyStack is built as two independently-deployable apps that share a Postgres database and a Redis instance:
| Component | Recommended host | Notes |
|---|---|---|
apps/web | Vercel | Next.js 16 with route groups and a BFF proxy. |
apps/api | Fly.io / Railway | NestJS 11 with Fastify + Helmet + structured error envelope. |
| Postgres 18 | Neon / Supabase | Use the connection-pooler URL as DATABASE_URL. |
| Redis 7.4 | Upstash | Required for refresh tokens, rate-limits, queues, cache. |
| Resend / SES | Set SMTP_* (or RESEND_API_KEY if you switch providers). | |
| Stripe | Stripe | Platform billing only — merchants integrate per-app. |
1. Postgres + Redis
Provision a Postgres 18 database (Neon's free tier is enough to start). Copy the pooled URL into DATABASE_URL and the direct URL into DIRECT_DATABASE_URL (Prisma uses the direct one for migrations).
Provision a Redis 7.4 instance (Upstash works great). Copy the TLS URL into REDIS_URL.
Then, run the migrations once against the new database:
DATABASE_URL=$PROD_DATABASE_URL \
DIRECT_DATABASE_URL=$PROD_DIRECT_DATABASE_URL \
pnpm --filter @keystack/db migrate deploy2. API on Fly.io
fly launch --copy-config --no-deploy --config apps/api/fly.toml
fly secrets set \
DATABASE_URL=... \
DIRECT_DATABASE_URL=... \
REDIS_URL=... \
JWT_ACCESS_SECRET=$(openssl rand -hex 32) \
JWT_REFRESH_SECRET=$(openssl rand -hex 32) \
API_KEY_ENCRYPTION_KEY=$(openssl rand -hex 32) \
STRIPE_PLATFORM_SECRET_KEY=... \
STRIPE_PLATFORM_WEBHOOK_SECRET=... \
CORS_ORIGINS=https://app.keystack.dev
fly deploy --config apps/api/fly.toml --dockerfile apps/api/DockerfileFly.io will use the included /health/ready endpoint as its readiness probe.
3. Web on Vercel
Connect the repository to Vercel and point the project root at apps/web. The included vercel.json already wires the install + build commands to use the monorepo lockfile and run a Prisma generate before the Next.js build.
Set these environment variables in the Vercel project:
| Name | Value |
|---|---|
NEXT_PUBLIC_API_URL | Public URL of your API (e.g. https://api.keystack.dev). |
INTERNAL_API_URL | Same as above, or a private endpoint if you're using Fly internal networking. |
WEB_COOKIE_SECRET | 32+ char random string. |
STRIPE_PUBLISHABLE_KEY | Used by the pricing/upgrade flow. |
4. DNS + TLS
Point app.keystack.dev (or whatever you choose) at the Vercel project and api.keystack.dev at the Fly app. Both providers handle TLS automatically.
5. Observability
- The API echoes a
X-Request-Idheader on every response — trace it across the BFF proxy and your client errors. - Every error response uses the same envelope:
{ "error": { "code", "message", "statusCode", "requestId", "details"? } }. - Audit logs are persisted to Postgres and viewable in the super-admin panel.
- Outgoing webhook deliveries are dispatched via BullMQ with exponential backoff and surfaced in
/v1/orgs/:slug/webhooks/:id/deliveries.