Skip to content

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:

ServiceImagePurpose
Caddycaddy:2.8-alpineReverse proxy, auto-TLS, security headers
APIBuilt from apps/api/DockerfileHono API server (read-only rootfs)
WebBuilt from apps/web/DockerfileAstro SSR dashboard (read-only rootfs)
PostgreSQLpostgres:16-alpinePrimary database
Redisredis:7-alpineJob queue, caching, rate limiting
Prometheusprom/prometheus:v2.52.0Metrics collection
Grafanagrafana/grafana:10.4.5Dashboards & visualization
Alertmanagerprom/alertmanager:v0.27.0Alert routing
Lokigrafana/loki:2.9.8Log aggregation
Promtailgrafana/promtail:2.9.8Log shipping
Redis Exporteroliver006/redis_exporterRedis metrics for Prometheus
Postgres Exporterprometheuscommunity/postgres-exporterPostgreSQL metrics

Security Hardening

Every container is hardened:

  • no-new-privileges: true — prevents privilege escalation
  • cap_drop: ALL — drops all Linux capabilities
  • read_only: true — read-only root filesystem (API, Web)
  • pids_limit — limits process count per container
  • cpus / mem_limit — resource constraints
  • Postgres and Redis bind to 127.0.0.1 only

Deploy Steps

  1. Prepare the server

    Install Docker, Node.js, pnpm, and Git per Prerequisites.

  2. Clone and configure

    Terminal window
    git clone https://github.com/LanternOps/breeze.git
    cd breeze
    cp .env.example .env.prod
  3. 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; do
    echo "${key}=$(openssl rand -hex 32)"
    done
    echo "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.com
    PUBLIC_API_URL=https://breeze.yourdomain.com/api/v1
    CORS_ALLOWED_ORIGINS=https://breeze.yourdomain.com
    DASHBOARD_URL=https://breeze.yourdomain.com
    PUBLIC_APP_URL=https://breeze.yourdomain.com
  4. Install dependencies

    Terminal window
    pnpm install
  5. Run the deploy script

    Terminal window
    ./scripts/prod/deploy.sh .env.prod

    The script will:

    1. Validate all required environment variables
    2. Start PostgreSQL and Redis
    3. Wait for database readiness (up to 60s)
    4. Run pnpm db:migrate
    5. Build and start all containers
    6. Wait for the health endpoint to respond
    7. Verify Prometheus and Grafana are healthy
  6. Verify the deployment

    Terminal window
    # Check health
    curl https://breeze.yourdomain.com/health
    # Check running containers
    docker compose -f docker/docker-compose.prod.yml ps
    # View logs
    docker compose -f docker/docker-compose.prod.yml logs -f api

Disabling Monitoring

To deploy without the monitoring stack:

Terminal window
ENABLE_MONITORING=false ./scripts/prod/deploy.sh .env.prod

This starts only Caddy, API, Web, PostgreSQL, and Redis.

Resource Tuning

Override default resource limits via environment variables:

Terminal window
# API (default: 1.5 CPU, 1536 MB RAM)
API_CPUS=2.0
API_MEM_LIMIT=2048m
# Web (default: 1.0 CPU, 768 MB RAM)
WEB_CPUS=1.5
WEB_MEM_LIMIT=1024m
# PostgreSQL (default: 1.0 CPU, 1024 MB RAM)
POSTGRES_CPUS=2.0
POSTGRES_MEM_LIMIT=2048m
# Redis (default: 0.5 CPU, 512 MB RAM)
REDIS_MAXMEMORY=1024mb
REDIS_MEM_LIMIT=1024m

Updating

To deploy a new version:

Terminal window
cd breeze
git pull origin main
pnpm install
./scripts/prod/deploy.sh .env.prod

The deploy script rebuilds containers with --build and runs pending migrations automatically.