Deployment
Production deploys via hostssh push-to-deploy on the self-hosted fleet. GitHub (origin) is code hosting only — pushing there does not deploy.
git push hostssh main
What a push does
- Checkout — the bare repo's
post-receivehook checks out to - Build + swap — image is built, the
casesolocontainer is replaced. - Migrate at container start —
npm start
/srv/hostssh/casesolo/checkout and runs hostssh-app deploy casesolo (Dockerfile build, port 3000, domains casesolo.com/www/app).
The pipeline itself runs no migrations.
(scripts/start-standalone.mjs) runs prisma migrate deploy against DATABASE_URL before binding, and refuses to start on failure — new code never serves an old schema. Opt out only with SKIP_MIGRATE_ON_START=true if a pipeline step owns migrations.
Manual fallback on the app box: docker exec casesolo node node_modules/prisma/build/index.js migrate deploy.
Build gotchas (all have broken deploys before)
| Symptom | Cause | Fix | |---|---|---| | Exit 255 / SIGABRT mid-build | Heap OOM (~8 GB peak) | Keep --max-old-space-size=12288 | | Module not found: tesseract.js / pdf-parse + RangeError | Built with webpack | Turbopack only — never --webpack | | Every route 502 after deploy | Standalone bound to container hostname (IPv6) | start-standalone.mjs forces HOSTNAME=0.0.0.0 | | Homepage etag unchanged after push | Build failed, old version still serving | Read the deploy hook build log |
Build identity: .dockerignore excludes .git, so the SHA resolves from NEXT_PUBLIC_BUILD_SHA / SOURCE_COMMIT build-args, then the .source-commit file, then local git rev-parse (dev only). Post-deploy: npm run verify:prod-public (optionally EXPECTED_BUILD_SHA=<sha>).
Production env (app-box deploy environment)
Minimum: DATABASE_URL, DIRECT_URL, JWT_SECRET (≥ 32 chars), USERMAILS_API_KEY, MAIL_FROM_EMAIL, NEXT_PUBLIC_APP_VERSION, NEXT_PUBLIC_BUILD_SHA, NEXT_PUBLIC_BUILD_TIME. Plus REDIS_URL, storage (R2_* or S3_*), Stripe/email/SMS/Sentry/AI keys as used. Startup fails closed without the usermails pair; pause sending with the disable_email_sms kill switch instead of removing credentials.
Scheduled dispatch (required for timed features to fire)
Scheduled emails, broadcasts, recurring tasks, and lead reminders need the cron dispatcher (src/lib/jobs/scheduled-dispatch.ts → /api/cron/dispatch):
- Set
CRON_SECRETin the deploy environment (else the endpoint 503s). - One trigger only — crontab every 15 min or a BullMQ repeatable job
(needs CASESOLO_ENABLE_BULLMQ_WORKERS=true), not both.
Rollback
migrate deploy is forward-only: ship a reversing migration, never delete from prisma/migrations/. Keep changes expand/contract-safe per database-migrations. Release procedure: release-runbook.