Production Deployment
Breeze ships with a single deploy script that brings up a fully hardened production stack on any Linux VPS.
What Gets Deployed
The production Docker Compose stack (docker/docker-compose.prod.yml) includes:
| Service | Image | Purpose |
|---|---|---|
| Caddy | caddy:2.8-alpine | Reverse proxy, auto-TLS, security headers |
| API | Built from apps/api/Dockerfile | Hono API server (read-only rootfs) |
| Web | Built from apps/web/Dockerfile | Astro SSR dashboard (read-only rootfs) |
| PostgreSQL | postgres:16-alpine | Primary database |
| Redis | redis:7-alpine | Job queue, caching, rate limiting |
| Prometheus | prom/prometheus:v2.52.0 | Metrics collection |
| Grafana | grafana/grafana:10.4.5 | Dashboards & visualization |
| Alertmanager | prom/alertmanager:v0.27.0 | Alert routing |
| Loki | grafana/loki:2.9.8 | Log aggregation |
| Promtail | grafana/promtail:2.9.8 | Log shipping |
| Redis Exporter | oliver006/redis_exporter | Redis metrics for Prometheus |
| Postgres Exporter | prometheuscommunity/postgres-exporter | PostgreSQL metrics |
Security Hardening
Every container is hardened:
no-new-privileges: true— prevents privilege escalationcap_drop: ALL— drops all Linux capabilitiesread_only: true— read-only root filesystem (API, Web)pids_limit— limits process count per containercpus/mem_limit— resource constraints- Postgres and Redis bind to
127.0.0.1only
Deploy Steps
-
Prepare the server
Install Docker, Node.js, pnpm, and Git per Prerequisites.
-
Clone and configure
Terminal window git clone https://github.com/LanternOps/breeze.gitcd breezecp .env.example .env.prod -
Generate all secrets
Terminal window # Run this helper to generate all required secrets at once:for key in JWT_SECRET APP_ENCRYPTION_KEY MFA_ENCRYPTION_KEY \ENROLLMENT_KEY_PEPPER MFA_RECOVERY_CODE_PEPPER \METRICS_SCRAPE_TOKEN SESSION_SECRET TURN_SECRET; doecho "${key}=$(openssl rand -hex 32)"doneecho "AGENT_ENROLLMENT_SECRET=$(openssl rand -hex 32)"echo "POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=')"echo "GRAFANA_ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -d '/+=')"Paste the output into
.env.prod. Then set:Terminal window BREEZE_DOMAIN=breeze.yourdomain.comPUBLIC_API_URL=https://breeze.yourdomain.com/api/v1CORS_ALLOWED_ORIGINS=https://breeze.yourdomain.comDASHBOARD_URL=https://breeze.yourdomain.comPUBLIC_APP_URL=https://breeze.yourdomain.com -
Install dependencies
Terminal window pnpm install -
Run the deploy script
Terminal window ./scripts/prod/deploy.sh .env.prodThe script will:
- Validate all required environment variables
- Start PostgreSQL and Redis
- Wait for database readiness (up to 60s)
- Run
pnpm db:migrate - Build and start all containers
- Wait for the health endpoint to respond
- Verify Prometheus and Grafana are healthy
-
Verify the deployment
Terminal window # Check healthcurl https://breeze.yourdomain.com/health# Check running containersdocker compose -f docker/docker-compose.prod.yml ps# View logsdocker compose -f docker/docker-compose.prod.yml logs -f api
Disabling Monitoring
To deploy without the monitoring stack:
ENABLE_MONITORING=false ./scripts/prod/deploy.sh .env.prodThis starts only Caddy, API, Web, PostgreSQL, and Redis.
Resource Tuning
Override default resource limits via environment variables:
# API (default: 1.5 CPU, 1536 MB RAM)API_CPUS=2.0API_MEM_LIMIT=2048m
# Web (default: 1.0 CPU, 768 MB RAM)WEB_CPUS=1.5WEB_MEM_LIMIT=1024m
# PostgreSQL (default: 1.0 CPU, 1024 MB RAM)POSTGRES_CPUS=2.0POSTGRES_MEM_LIMIT=2048m
# Redis (default: 0.5 CPU, 512 MB RAM)REDIS_MAXMEMORY=1024mbREDIS_MEM_LIMIT=1024mUpdating
To deploy a new version:
cd breezegit pull origin mainpnpm install./scripts/prod/deploy.sh .env.prodThe deploy script rebuilds containers with --build and runs pending migrations automatically.