Deploying LocaleCI Cloud¶
A step-by-step runbook to take apps/cloud from repo to production. This closes
the deploy-dependent tail of the hardening work (observability sinks, KV rate
limiting, route typecheck, load tests).
The whole platform runs serverless: Vercel (app) · Neon (Postgres + pgvector) · Upstash (KV/Redis) · Sentry (errors) · Stripe (billing) · Resend (email). Budget for production is roughly $0–50/mo at low volume.
0. Prerequisites¶
- A GitHub account (OAuth app) and the
Martinez1991/localecirepo. - Accounts: Vercel, Neon, Upstash, Sentry, Stripe, Resend.
pnpmand the Vercel CLI (npm i -g vercel).
1. Install deploy dependencies¶
The monorepo keeps these out of CI to stay light; install them in apps/cloud:
cd apps/cloud
pnpm add next@^15 react@^19 react-dom@^19 \
drizzle-orm@^0.38 drizzle-kit @neondatabase/serverless \
next-auth@^5 @auth/core stripe@^17 resend @paralleldrive/cuid2 \
@upstash/ratelimit @upstash/redis @sentry/nextjs
2. Provision the database (Neon + pgvector)¶
-- In the Neon SQL editor, once:
CREATE EXTENSION IF NOT EXISTS vector;
# Generate and apply the Drizzle schema (users, orgs, org_members, org_invites,
# api_keys, usage_events, translation_vectors, reviews, jobs, audit_log, ...):
pnpm db:generate
pnpm db:migrate
# Seed each existing org owner into org_members as 'owner' (RBAC back-compat):
# INSERT INTO org_members (org_id, user_id, role)
# SELECT id, owner_id, 'owner' FROM orgs ON CONFLICT DO NOTHING;
3. Wire the production sinks (closes #9, #11, #18)¶
The adapters are already written and unit-tested — you only plug credentials:
- Observability (#9):
sentry.server.config.ts,sentry.edge.config.ts, andsrc/instrumentation.tsare provided. Insrc/server/guard.ts, build the logger with the Sentry sink:createLogger(ctx, sentrySink(Sentry))(fromsrc/lib/logSink.ts, tested). Errors/warns forward to Sentry; all lines emit redacted JSON. Set an alert on thequotaAlertfield. - Rate limiting KV (#11): import
kvRateLimiterfromsrc/server/redis.ts(an@upstash/redis-backedKvRateLimiter, tested) and use it insrc/server/guard.tsin place of the in-memorylimiter. - Hot TM cache (#18): in
src/lib/platformProvider.ts, replace theInMemoryLruwithkvCachefromsrc/server/redis.ts. Point dashboard/usage reads at the read replica viadbRead(src/db/replica.ts).
4. Typecheck the routes (closes #12)¶
pnpm --filter @localeci/cloud typecheck:full # now that next/drizzle/... are installed
5. Environment variables¶
Copy apps/cloud/.env.example and fill it in. Set the same in Vercel Project →
Settings → Environment Variables:
| Var | Notes |
|---|---|
DATABASE_URL / DATABASE_URL_REPLICA |
Neon primary / read replica |
AUTH_SECRET, AUTH_GITHUB_ID/SECRET |
Auth.js + GitHub OAuth app |
APP_URL |
e.g. https://localeci.dev (CSRF origin allow-list) |
ANTHROPIC_API_KEY (or OPENAI_API_KEY) |
platform LLM key |
LOCALECI_RETENTION |
false (zero-retention default) |
KV_REST_API_URL/TOKEN |
Upstash |
SENTRY_DSN |
error tracking |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_{PRO,TEAM,SCALE} |
billing |
WORKER_SECRET |
protects /api/worker and /api/retention |
PLAYGROUND_RATE_LIMIT, PLAYGROUND_DAILY_CAP_USD, ORG_DAILY_CAP_USD |
guardrails |
RESEND_API_KEY |
transactional email |
6. Cron jobs (Vercel Cron)¶
vercel.json:
{
"crons": [
{ "path": "/api/worker", "schedule": "*/1 * * * *" },
{ "path": "/api/retention", "schedule": "0 3 * * *" }
]
}
Authorization: Bearer $WORKER_SECRET — configure the cron secret.
7. Stripe¶
- Create the Pro/Team/Scale prices; put their ids in
STRIPE_PRICE_*. - Add a webhook to
/api/stripe/webhookforcustomer.subscription.*; put the signing secret inSTRIPE_WEBHOOK_SECRET.
8. Deploy & verify¶
vercel --prod
BASE_URL=https://localeci.dev ./scripts/smoke.sh # status-code smoke test (docs/smoke-test.md)
/playground translates and throttles (429) under load — see load/.
- POST /api/v1/keys (as an admin) issues a key; the action is in audit_log.
- POST /api/v1/privacy/export returns your data; .../delete scrubs PII.
- k6 run load/playground.k6.js meets the p95/p99 SLOs.
9. GitHub Action (public product)¶
The Action ships its compiled packages/action/dist (committed; CI verifies
freshness). Tag a release and reference it as
uses: localeci/localeci/packages/action@<sha>.