Skip to content

Quickstart

Get a working Breeze instance running with a single docker compose up.

The guided installer downloads the templates, generates every required secret in the right format, checks that the version you pick has published container images, and starts the stack:

Terminal window
mkdir breeze && cd breeze
curl -fsSLO https://raw.githubusercontent.com/lanternops/breeze/main/scripts/guided-setup.sh
bash guided-setup.sh

Prefer to configure .env yourself? The manual path is below.

  1. Download the compose file and environment template

    Terminal window
    mkdir breeze && cd breeze
    curl -fsSLO https://raw.githubusercontent.com/lanternops/breeze/main/docker-compose.yml
    curl -fsSLO https://raw.githubusercontent.com/lanternops/breeze/main/.env.example
    # Caddy config — the compose file bind-mounts this, so it must exist on disk first
    curl -fsSL --create-dirs -o docker/Caddyfile.prod https://raw.githubusercontent.com/lanternops/breeze/main/docker/Caddyfile.prod
    cp .env.example .env
  2. Set your domain

    Terminal window
    # Domain (use "localhost" for local testing — see the headless note below)
    BREEZE_DOMAIN=localhost
    ACME_EMAIL=you@example.com
  3. Generate the secrets

    .env.example ships placeholder values for every secret. Replace them in place — edit the existing line for each key rather than appending a second copy at the end of the file.

    This one-liner rewrites each placeholder with a fresh value:

    Terminal window
    for key in JWT_SECRET SESSION_SECRET AGENT_ENROLLMENT_SECRET \
    APP_ENCRYPTION_KEY MFA_ENCRYPTION_KEY ENROLLMENT_KEY_PEPPER \
    MFA_RECOVERY_CODE_PEPPER METRICS_SCRAPE_TOKEN; do
    sed -i "s|^${key}=.*|${key}=$(openssl rand -hex 32)|" .env
    done
    # Must be canonical base64 decoding to >= 32 bytes, and must not reuse JWT_SECRET
    sed -i "s|^PARTNER_API_CURSOR_SIGNING_KEY=.*|PARTNER_API_CURSOR_SIGNING_KEY=$(openssl rand -base64 32)|" .env
    # Database and Redis. Redis auth is REQUIRED — the API refuses to start without it.
    POSTGRES_PASSWORD="$(openssl rand -base64 24 | tr -d '/+=')"
    REDIS_PASSWORD="$(openssl rand -hex 32)"
    sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=${POSTGRES_PASSWORD}|" .env
    sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=${REDIS_PASSWORD}|" .env
    sed -i "s|^REDIS_URL=.*|REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379|" .env

    On macOS, use sed -i '' instead of sed -i.

  4. Check the version pin

    .env.example pins BREEZE_VERSION to a specific release. Confirm it is the one you want before starting:

    Terminal window
    grep '^BREEZE_VERSION=' .env

    To upgrade later, set it to a newer release from the releases page and re-run docker compose up -d.

  5. Start Breeze

    Terminal window
    docker compose up -d

    On first boot the API container automatically:

    • Runs database migrations
    • Seeds the default admin user
    • Starts the background job workers
  6. Verify

    Terminal window
    # Wait for API to be healthy (~30s on first boot)
    docker compose logs -f api --since 1m

    Once you see Breeze API running, open the dashboard:

    • Dashboard: https://localhost (accept the self-signed cert)
    • Health check: curl -k https://localhost/health

Reaching the dashboard from another machine

Section titled “Reaching the dashboard from another machine”

BREEZE_DOMAIN becomes Caddy’s site address, and Caddy only answers requests whose Host header matches it. With BREEZE_DOMAIN=localhost the ports are published on all interfaces, but a browser on your laptop sends Host: 192.168.1.50, which matches nothing — so a minimal/headless server install has no reachable UI even though curl -k https://localhost/health returns OK on the box itself.

Pick whichever fits:

  • SSH tunnel — leave BREEZE_DOMAIN=localhost and forward the port:

    Terminal window
    ssh -L 8443:127.0.0.1:443 user@your-server
    # then browse to https://localhost:8443
  • LAN address — serve the machine’s IP or hostname directly:

    Terminal window
    BREEZE_DOMAIN=192.168.1.50
    PUBLIC_APP_URL=https://192.168.1.50
    DASHBOARD_URL=https://192.168.1.50

    Caddy cannot get a public certificate for a private address, so it serves its internal self-signed cert — accept the browser warning.

  • Real domain — the production path. See Production Deploy.

Whichever you choose, PUBLIC_APP_URL must be the URL users actually visit: it is what agent installers and portal links are built from.

Container Port Purpose
breeze-caddy 80, 443 Reverse proxy + auto-TLS
breeze-api 3001 (internal) Hono API server
breeze-web 4321 (internal) Astro SSR dashboard
breeze-portal 4322 (internal) Customer portal, served under /portal
breeze-postgres 5432 (internal) PostgreSQL 16 database
breeze-redis 6379 (internal) Redis 7 (BullMQ + caching)
breeze-coturn 3478 (host) TURN relay for WebRTC remote desktop (only with --profile turn)

Download and install the Breeze agent on a device. BREEZE_SERVER must be the URL of your Breeze server as the target device can reach it — not localhost, which on the target device means the target device itself:

Terminal window
# On the target device:
# The enrollment token is REQUIRED — grab it from the Add Device dialog.
curl -fsSL https://breeze.example.com/api/v1/agents/install.sh | \
BREEZE_SERVER=https://breeze.example.com \
BREEZE_ENROLL_TOKEN=<your-enrollment-token> \
bash

If your server is configured with an org enrollment secret, pass it as an optional extra gate alongside the token (never instead of it):

Terminal window
curl -fsSL https://breeze.example.com/api/v1/agents/install.sh | \
BREEZE_SERVER=https://breeze.example.com \
BREEZE_ENROLL_TOKEN=<your-enrollment-token> \
BREEZE_ENROLLMENT_SECRET=<your-enrollment-secret> \
bash

See Agent Installation for detailed instructions per platform.

For production deployment with a real domain and monitoring, see Production Deploy.