Deployment guide
Prerequisites
- Node.js 20+
- PostgreSQL with migrations from
db/migrations/applied in order (through `011_gtm_use_case_statuses.sql` for operator GTM status + `010_erie_directory_seed.sql` for Erie.pro directory demo + `009_stripe_webhook_idempotency_billing_cols.sql` for Stripe webhook idempotency +billing_subscriptionsStripe linkage; `007_billing_entitlements_audit.sql` for billing gates + operator audit log). - LEAD_OS_AUTH_SECRET (required for API middleware signature and operator JWTs).
- CRON_SECRET (or LEAD_OS_AUTH_SECRET as fallback for cron Bearer /
x-cron-secret) if you call cron routes; cron POST/GET handlers validate this in-route as well as in middleware. - STRIPE_WEBHOOK_SECRET / STRIPE_SECRET_KEY for
/api/billing/webhookand `/api/billing/stripe/webhook` (public; configure both URLs in Stripe if you use the new path). - Optional:
REDIS_URLfor BullMQ pricing queues and distributed scheduling.
Local development
cd lead-os-hosted-runtime-wt-hybrid
npm install
cp .env.example .env.local # edit values
npm run devIn another terminal (when REDIS_URL is set):
npm run workerUse .env.example as the authoritative list of variables. For a minimal local stack with Postgres and Redis:
docker compose up -dDocker image
docker build -t lead-os-hosted-runtime .
docker run --env-file .env -p 3000:3000 lead-os-hosted-runtimeEnsure DATABASE_URL (or LEAD_OS_DATABASE_URL) points at your Postgres instance and that migrations have run.
Worker image (BullMQ)
docker build --target worker -t lead-os-worker .
docker run --env-file .env lead-os-workerThe worker stage runs node --experimental-strip-types src/runtime/worker-entry.ts. It needs `REDIS_URL`, database credentials, and the same pricing env vars as the web app. Use docker compose up to start app, db, redis, and worker together.
Vercel
- Configure all secrets from
.env.examplein the Vercel project settings. - After deploy, smoke-test `/`, `/docs`, `/api/health`, and `/marketplace` (if the marketplace API is empty or errors, the UI shows an explicit sample data banner — ensure production inventory is wired before go-live marketing).
- Cron entries are defined in
vercel.json(e.g. pricing tick). VerifyCRON_SECRETmatches what Vercel sends if you validate cron requests. - **
/api/cron/*POST/GET handlers (discovery, optimize, experiments, pricing-tick, nurture) requireCRON_SECRET(or `LEAD_OS_AUTH_SECRET` as fallback) in the handler itself — session/API-key access alone is not enough to invoke those mutation endpoints. Use/api/operator/actions** (authenticated) for operator-triggered equivalents where applicable. - Redis: use a managed Redis URL for production if you rely on BullMQ; otherwise pricing runs in degraded/no-queue mode.
Boot order
- Next.js starts;
src/instrumentation.tscallsstartPricingRuntimeWeb()— no BullMQ workers whenREDIS_URLis set (usenpm run worker). - Database pool connects when
DATABASE_URLis set; migrations can run viainitializeDatabase()where wired. - Pricing jobs require schema from migrations 005–007 (billing optional until
LEAD_OS_BILLING_ENFORCE=true).
Rollback
- Disable side effects: set
SYSTEM_ENABLED=falseandENABLE_LIVE_PRICING=false, redeploy. - Restore database from snapshot if schema or data migrations require reversal.
Migration verification
npm run verify:migrationsWith DATABASE_URL set, the script compares files in db/migrations/ to rows in lead_os_migrations and exits non-zero if migrations are missing from the database.
Backups
- Use your provider’s continuous backup / PITR for Postgres (recommended).
- Automated dump (timestamped): from
lead-os-hosted-runtime-wt-hybrid, withLEAD_OS_DATABASE_URLorDATABASE_URLset: bash scripts/backup-db.sh— writes./backups/lead-os-<UTC>.sql.gz(override directory withLEAD_OS_BACKUP_DIR).- Restore (destructive): set
LEAD_OS_RESTORE_TARGET_URLto a dedicated database (e.g. staging or a disposable instance), then: bash scripts/restore-db.sh ./backups/lead-os-<timestamp>.sql.gz- Archive sanity check:
npm run verify:backup-archive -- ./backups/lead-os-<timestamp>.sql.gz(gzip magic + minimum size). - Integrity after restore: run
npm run verify:migrationsagainst the restored URL, then smoke-test critical flows (auth, intake, operator, pricing tick).
Hardening reference
See SYSTEM-HARDENING.md for middleware tenant/billing gates, idempotency, rate limits, and phased security work.