How to deploy Sentinel to production.
Sentinel has two components that need to run:
- Next.js App — Dashboard and API endpoints
- Worker Process — Background job processing
You can run both on one server or split them. This guide covers the split approach using managed services.
| Component | Service | Why |
|---|---|---|
| Next.js | Vercel | Zero-config, great DX, automatic previews |
| Workers | Railway | Easy process management, good logs |
| Database | Supabase | Free tier is generous, managed Postgres |
| Redis | Upstash | Serverless Redis, pay-per-request |
Total cost for a small team: ~$20-50/month after free tiers.
- Create account at supabase.com
- Create new project
- Go to Settings → Database → Connection string
- Copy the URI (use "Transaction" mode for pooling)
DATABASE_URL=postgresql://postgres:[password]@db.[ref].supabase.co:5432/postgres
Run migrations:
DATABASE_URL="your-supabase-url" pnpm db:push- Create account at upstash.com
- Create new Redis database
- Copy the connection URL
REDIS_URL=rediss://default:[password]@[region].upstash.io:6379
Note the rediss:// (with double s) for TLS.
Update your GitHub App settings:
- Go to https://github.com/settings/apps/your-app
- Update Webhook URL:
https://your-vercel-domain.com/api/webhooks/github - Ensure webhook secret matches your environment
- Push code to GitHub
- Import project in Vercel
- Add environment variables:
DATABASE_URL=postgresql://...
REDIS_URL=rediss://...
GITHUB_APP_ID=123456
GITHUB_WEBHOOK_SECRET=your-secret
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----..."
ADMIN_API_KEY=generate-a-random-string
NEXT_PUBLIC_APP_URL=https://your-domain.vercel.app
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
RESEND_API_KEY=re_...
ALERT_EMAIL_TO=alerts@company.com
- Deploy
The Next.js app handles:
- Dashboard UI
- tRPC API
- Webhook receiver
- Admin endpoints
Workers need a long-running process, not serverless. Railway works well.
- Create account at railway.app
- Create new project from GitHub
- Add same environment variables as Vercel
- Set start command:
pnpm workers - Deploy
The worker process handles:
- Webhook job processing
- AI detection analysis
- Scheduled metrics jobs
- Alert evaluation
- Notification delivery
If you prefer running everything on one VPS:
# PM2 for process management
npm install -g pm2
# Start Next.js
pm2 start "pnpm start" --name sentinel-web
# Start workers
pm2 start "pnpm workers" --name sentinel-workers
# Save and enable startup
pm2 save
pm2 startupComplete list for production:
# Database (required)
DATABASE_URL=postgresql://user:pass@host:5432/db
# Redis (required)
REDIS_URL=rediss://user:pass@host:6379
# GitHub App (required)
GITHUB_APP_ID=123456
GITHUB_WEBHOOK_SECRET=your-webhook-secret
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...multi-line key...
-----END RSA PRIVATE KEY-----"
# Admin (required)
ADMIN_API_KEY=long-random-string
# App URL (required for notifications)
NEXT_PUBLIC_APP_URL=https://sentinel.yourcompany.com
# Notifications (configure what you need)
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
RESEND_API_KEY=re_xxxxxxxxxxxx
ALERT_EMAIL_TO=alerts@company.com
ALERT_EMAIL_FROM=Sentinel <alerts@yourcompany.com>
PAGERDUTY_ROUTING_KEY=your-routing-key
# Logging
LOG_LEVEL=info- Go to Project Settings → Domains
- Add your domain
- Update DNS with provided records
- SSL is automatic
After domain setup, update webhook URL in GitHub App settings.
curl https://your-domain.com/api/health
# Should return 200Check Railway logs for:
INFO: Workers running
INFO: Registered repeatable job: compute-metrics-daily
curl -X POST "https://your-domain.com/api/admin/metrics/compute?date=2026-01-01" \
-H "X-Admin-Key: your-key"
# Should return { success: true, ... }- Built-in analytics and logs
- Function invocation metrics
- Error tracking
- Process logs
- Memory/CPU graphs
- Restart alerts
Sentry — Error tracking with stack traces
pnpm add @sentry/nextjsBetterStack (formerly Logtail) — Log aggregation
Both have generous free tiers.
Supabase includes:
- Daily automatic backups (7 days retention on free)
- Point-in-time recovery (paid plans)
For extra safety:
# Manual backup
pg_dump $DATABASE_URL > backup-$(date +%Y%m%d).sqlUpstash includes persistence. For BullMQ, jobs are recoverable from the database if Redis dies — just re-queue based on code_events without code_attribution.
- Check slow query logs in Supabase
- Add missing indexes (check ARCHITECTURE.md)
- Consider read replica for dashboard queries
- Check queue depths:
scheduledQueue.getJobCounts() - Increase concurrency in
workers/index.ts - Run multiple worker instances (Railway makes this easy)
- Webhook handler should queue immediately and return
- Check if signature verification is slow (shouldn't be)
- Verify Redis connection is healthy
- Check
GITHUB_WEBHOOK_SECRETmatches GitHub App settings - Ensure raw body is preserved (Next.js API route config)
- Check
DATABASE_URLformat - Verify IP allowlist in Supabase (Settings → Database → Network)
- Use connection pooler URL for serverless
- Verify
REDIS_URLuses TLS (rediss://) - Check Upstash dashboard for connection issues
- Check Railway logs for errors
- Verify Redis connection in worker process
- Check job queue:
await webhookQueue.getJobCounts()
- Check notification worker logs
- Verify Slack/Resend/PagerDuty credentials
- Test webhook URLs manually with curl