Quickstart
Get a working Breeze instance running with a single docker compose up.
Guided setup (recommended)
Section titled “Guided setup (recommended)”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:
mkdir breeze && cd breezecurl -fsSLO https://raw.githubusercontent.com/lanternops/breeze/main/scripts/guided-setup.shbash guided-setup.shPrefer to configure .env yourself? The manual path is below.
Manual setup
Section titled “Manual setup”-
Download the compose file and environment template
Terminal window mkdir breeze && cd breezecurl -fsSLO https://raw.githubusercontent.com/lanternops/breeze/main/docker-compose.ymlcurl -fsSLO https://raw.githubusercontent.com/lanternops/breeze/main/.env.example# Caddy config — the compose file bind-mounts this, so it must exist on disk firstcurl -fsSL --create-dirs -o docker/Caddyfile.prod https://raw.githubusercontent.com/lanternops/breeze/main/docker/Caddyfile.prodcp .env.example .env -
Set your domain
Terminal window # Domain (use "localhost" for local testing — see the headless note below)BREEZE_DOMAIN=localhostACME_EMAIL=you@example.com -
Generate the secrets
.env.exampleships 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; dosed -i "s|^${key}=.*|${key}=$(openssl rand -hex 32)|" .envdone# Must be canonical base64 decoding to >= 32 bytes, and must not reuse JWT_SECRETsed -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}|" .envsed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=${REDIS_PASSWORD}|" .envsed -i "s|^REDIS_URL=.*|REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379|" .envOn macOS, use
sed -i ''instead ofsed -i. -
Check the version pin
.env.examplepinsBREEZE_VERSIONto a specific release. Confirm it is the one you want before starting:Terminal window grep '^BREEZE_VERSION=' .envTo upgrade later, set it to a newer release from the releases page and re-run
docker compose up -d. -
Start Breeze
Terminal window docker compose up -dOn first boot the API container automatically:
- Runs database migrations
- Seeds the default admin user
- Starts the background job workers
-
Verify
Terminal window # Wait for API to be healthy (~30s on first boot)docker compose logs -f api --since 1mOnce you see
Breeze API running, open the dashboard:- Dashboard:
https://localhost(accept the self-signed cert) - Health check:
curl -k https://localhost/health
- Dashboard:
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=localhostand 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.50PUBLIC_APP_URL=https://192.168.1.50DASHBOARD_URL=https://192.168.1.50Caddy 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.
What’s Running
Section titled “What’s Running”| 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) |
Next: Enroll Your First Agent
Section titled “Next: Enroll Your First Agent”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:
# 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> \ bashIf your server is configured with an org enrollment secret, pass it as an optional extra gate alongside the token (never instead of it):
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> \ bashSee Agent Installation for detailed instructions per platform.
For production deployment with a real domain and monitoring, see Production Deploy.