Production deployment
Carabase runs on your always-on Mac, reachable only over Tailscale — no public ports, no cloud dependency, no Docker-in-a-VM on the Mac itself. Deploys are a local source build restarted via launchd.
Components
Section titled “Components”| Script | Runs where | What it does |
|---|---|---|
scripts/install-prod-service.sh | prod Mac | One-time: installs the launchd LaunchAgent |
scripts/deploy-prod.sh | prod Mac | Every deploy: backup → pull → build → restart → smoke → auto-rollback |
scripts/rollback-prod.sh | prod Mac | Manual rollback to a specific sha/tag |
scripts/smoke-prod.ts | anywhere | Read-only HTTP smoke against a running host URL |
All four scripts load .env.production via scripts/load-env.sh so they share a single, tested env-resolution path.
The launchd service
Section titled “The launchd service”pnpm install-prod-service writes ~/Library/LaunchAgents/dev.carabase.host.prod.plist with:
RunAtLoad=true— start on loginKeepAlive.SuccessfulExit=false— auto-restart on crashThrottleInterval=10— don’t flap if something is badly brokenStandardOutPath=~/.carabase/logs/prod.logStandardErrorPath=~/.carabase/logs/prod.errProgramArguments=./scripts/with-env.sh prod -- node dist/server.js
The installer refuses if .env.production or dist/server.js don’t exist — a flapping service that can’t start up is worse than a missing service.
What pnpm deploy:prod does
Section titled “What pnpm deploy:prod does”Every run:
- Loads
.env.production, classifiesDATABASE_URL, refuses if the db name isn’t*_prod/*_production - Acquires a lock file at
~/.carabase/deploys/.lockso two deploys can’t race - Records the current git sha (
PRE_SHA) and branch, writes audit metadata to~/.carabase/deploys/<ts>.json - Runs
pnpm backup:prod(every change gets a backup first).--skip-backupexists but prints a loud warning and should only be used in emergencies git fetch origin --tags+git checkout --detach <target>(defaultorigin/main, override with--tag v0.2.0or--ref 1a2b3c)pnpm install --frozen-lockfilepnpm db:migrate:prod(Drizzle handles forward migrations only)pnpm buildlaunchctl kickstart -k gui/<uid>/dev.carabase.host.prod(atomic restart)- Poll
http://127.0.0.1:$PORT/api/v1/healthuntil 200 or 30s - Run
pnpm smoke:prod --url <live-url>— the read-only prod smoke
On any failure after step 9: auto-rollback. git checkout $PRE_SHA → pnpm install → pnpm build → launchctl kickstart -k → best-effort health check → exit 1. The audit metadata is updated with status: rolled_back + failed_at_sha + rolled_back_to.
--dry-run prints the plan without executing. Every run tees into ~/.carabase/deploys/<ts>.log.
Daily workflow after the first deploy
Section titled “Daily workflow after the first deploy”# On your dev machine — make changes, open PR, merge to maingit push# → CI runs lint + test + smoke:e2e# → PR merged to main
# On the prod Mac (or ssh in via Tailscale)cd ~/Code/carabase-hostpnpm deploy:prod# → runs backup → pulls main → migrate → build → restart → smoke# → auto-rollback on any failure# → audit log at ~/.carabase/deploys/<ts>.logTo pin prod to a specific tagged version:
pnpm deploy:prod --tag v0.2.0To emergency-roll back to the previous tag (doesn’t run migrations — restore from backup if the schema needs to move):
pnpm rollback:prod --to v0.1.0Read-only prod smoke
Section titled “Read-only prod smoke”scripts/smoke-prod.ts is the post-deploy gate. Unlike the E2E smoke (which creates a scratch DB + seeds + spawns a subprocess), this one:
- Takes
--url <host-url>and hits it with realfetch()calls - Does NOT create, seed, or mutate any DB state
- Does NOT require a known workspace id to run basic liveness
- With
--workspace-id <uuid>, additionally runs read-only scoped checks: model-routing GET, folios list, canonical entities list
Unauthenticated checks always run:
GET /api/v1/health→ 200GET /health→ 200GET /api/v1/folioswithout header → 400 (confirms the auth middleware is wired — a critical security regression otherwise)
First deploy
Section titled “First deploy”See docs/FIRST_DEPLOY.md in the repo for the complete one-time setup checklist: prereqs, env files, setup:envs, first build, backup cron install, service install, first deploy, first tag, desktop reconnection, backup/restore stress test.