Documentation

KeyStack is built as two independently-deployable apps that share a Postgres database and a Redis instance:

ComponentRecommended hostNotes
apps/webVercelNext.js 16 with route groups and a BFF proxy.
apps/apiFly.io / RailwayNestJS 11 with Fastify + Helmet + structured error envelope.
Postgres 18Neon / SupabaseUse the connection-pooler URL as DATABASE_URL.
Redis 7.4UpstashRequired for refresh tokens, rate-limits, queues, cache.
EmailResend / SESSet SMTP_* (or RESEND_API_KEY if you switch providers).
StripeStripePlatform 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 deploy

2. 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/Dockerfile

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

NameValue
NEXT_PUBLIC_API_URLPublic URL of your API (e.g. https://api.keystack.dev).
INTERNAL_API_URLSame as above, or a private endpoint if you're using Fly internal networking.
WEB_COOKIE_SECRET32+ char random string.
STRIPE_PUBLISHABLE_KEYUsed 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-Id header 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.