Deployment guide
Vigil deploys as two processes and one database: the Next.js app, the worker, and PostgreSQL 18+. Nothing else is required.
1. Docker Compose (single host)
The included docker-compose.yml is
production-shaped: Postgres with a persistent volume, a one-shot
migrate service, the standalone app image, and the worker image.
# .env next to docker-compose.yml
BETTER_AUTH_SECRET=<openssl rand -base64 32>
APP_URL=https://vigil.yourdomain.com
POSTGRES_PASSWORD=<strong password>
docker compose up --build -d
Put a TLS-terminating reverse proxy in front of port 3000 (Caddy shown; nginx/Traefik equivalent):
vigil.yourdomain.com {
reverse_proxy localhost:3000
}
Upgrading a running stack
git pull
docker compose build
docker compose up -d # migrate runs first, then app/worker restart
2. Managed platforms
- App (Next.js): Can be deployed to Vercel (Serverless) or any container platform (Render, Fly.io, Railway). If deploying to Vercel, simply connect your repository and configure the environment variables.
- Worker (Background checks): Because Vercel is serverless, it does not support long-running processes required for queue polling. You must run the worker process on a container/server platform. Fly.io (free tier supports small persistent VMs) or paid background workers on Render (starts at $7/mo) or Railway are ideal. Configure it to build using
npm ciand start usingnpm run worker(or target theworkerstage in the Dockerfile). - Database: Any managed PostgreSQL 18+ database (required for native
uuidv7()). Neon.tech is highly recommended and offers a free tier. - Migrations: Run
npx drizzle-kit migrateas a pre-deploy or release step, or run it locally pointing to your production database before launching.
Hybrid Setup Example (Vercel + Neon + Fly.io/Render Worker)
- Database: Set up a database on Neon, copy the connection string.
- Frontend (Vercel): Connect your GitHub repo, set the Framework Preset to Next.js. Add
DATABASE_URL(Neon),BETTER_AUTH_SECRET(generate one), andAPP_URL(your deployment URL) to the Environment Variables. - Worker: Create a new background worker/VM on Fly.io (free) or Render (paid). Link the same repository. Add the same
DATABASE_URL,BETTER_AUTH_SECRETandAPP_URLenvironment variables (the worker embedsAPP_URLin incident notification links). Set the Start Command tonpm run worker. - Communication: Both processes will securely coordinate through the Neon database using
pg-boss. No Redis or open network ports between the frontend and worker are needed.
3. Bare metal / VM without Docker
npm ci
npm run build
npm run db:migrate
# standalone output does not include static assets — copy them in once per build:
cp -r .next/static .next/standalone/.next/static
cp -r public .next/standalone/public
# process manager of your choice (systemd/pm2):
node .next/standalone/server.js # app, PORT=3000
npm run worker # worker
Environment
See sample.env.production for the full
annotated template. Required: DATABASE_URL, BETTER_AUTH_SECRET,
APP_URL.
Operational notes
- Health:
GET /api/healthreturns 200 when the app can reach Postgres and 503 otherwise — point load-balancer/orchestrator probes at it (the compose file already does). The worker logsworker startedon boot and exits non-zero on fatal errors — wire it into your restart policy. - Logs: both processes emit structured JSON (pino) on stdout. Set
LOG_LEVEL=debugtemporarily for check-level detail. - Backups: one
pg_dumpcovers everything — domain data, auth, audit trail and the job queue (pgbossschema is disposable; it rebuilds on worker start). - Email: set
RESEND_API_KEY(andEMAIL_FROM, a Resend-verified sender) on both the app and the worker to deliver incident emails via Resend; without a key they fall back to structured logs. A future SMTP/SES provider is anotherEmailTransportinsrc/modules/notifications. - Webhooks: configured per organization under
Settings → Notifications — no env needed. Receivers verify the
X-Vigil-Signatureheader (HMAC-SHA-256 of the raw body). See ARCHITECTURE.md §8 for the payload and event list. - Scaling: app and worker are independently horizontal; the queue serializes per-monitor work. See ARCHITECTURE.md §10 for the pressure → response table.