PostgreSQL password. Also used to derive the unprivileged request-pool credential (see below) unless DATABASE_URL_APP or BREEZE_APP_DB_PASSWORD is set.
POSTGRES_DB
breeze
Database name
POSTGRES_PORT
5432
PostgreSQL port
DATABASE_URL
—
Auto
Full connection string (constructed from above in Docker)
DATABASE_URL_APP
—
One of these three (prod)
Explicit connection string for the unprivileged request role (breeze_app). Preferred, and required for multi-host / high-availability DATABASE_URL values, which cannot be derived automatically.
BREEZE_APP_DB_PASSWORD
—
One of these three (prod)
Password for the breeze_app role, used to derive its connection string from DATABASE_URL.
In production, Breeze runs request handlers as an unprivileged, row-level-security-enforced database role (breeze_app) rather than as the database owner. The API refuses to start unless it can configure that role — set one of DATABASE_URL_APP, BREEZE_APP_DB_PASSWORD, or POSTGRES_PASSWORD. This check runs whenever NODE_ENV=production, including when AUTO_MIGRATE=false. The API also refuses to start if it detects it is running as a SUPERUSER or BYPASSRLS role — if you were previously running as the postgres superuser, switch to a NOSUPERUSER/NOBYPASSRLS role. After doing so you may notice rows that were previously visible to a tenant “disappear”; that is row-level security working correctly, not data loss. See the request-database-role runbook.
Shared secret for agent enrollment. Generate: openssl rand -hex 32
APP_ENCRYPTION_KEY
—
Yes
AES encryption key for sensitive data at rest
MFA_ENCRYPTION_KEY
—
Yes
Encryption key for MFA secrets
ENROLLMENT_KEY_PEPPER
—
Yes
HMAC pepper for enrollment key hashing
MFA_RECOVERY_CODE_PEPPER
—
Yes
HMAC pepper for recovery code hashing
ENROLLMENT_KEY_DEFAULT_TTL_MINUTES
60
Default enrollment key expiry
ENROLLMENT_KEY_CLEANUP_ENABLED
true
Set to false to disable the daily sweep that purges expired enrollment keys
ENROLLMENT_KEY_PURGE_AFTER_DAYS
7
Grace period (days past expiry) before an expired enrollment key is purged
SESSION_SECRET
—
Yes
Express session signing secret
SESSION_MAX_AGE
86400000
Session max age in ms (24h)
Access-token (15m) and refresh-token (7d) lifetimes are fixed in code
(apps/api/src/services/jwt.ts) — short-lived access tokens backed by
refresh-token rotation — and are intentionally not configurable via
deployment env vars.
Public API URL used for generated agent installers and shareable links. The bundled docker-compose.yml derives this from BREEZE_DOMAIN. If you use a custom compose file, you must set PUBLIC_API_URL in .envand map it into the api service environment: block, or Generate Link / Download Installer will fail with Server URL not configured.
API_URL
—
Legacy fallback for PUBLIC_API_URL, honored only by enrollment, installer, and MCP-invite code paths. Auto-update, dev-push, and a few other routes read PUBLIC_API_URL only — prefer setting PUBLIC_API_URL.
BREEZE_DOMAIN
—
Yes (prod)
Domain for Caddy TLS provisioning
ACME_EMAIL
—
Yes (prod)
Email for Let’s Encrypt certificate notifications
CORS_ALLOWED_ORIGINS
—
Comma-separated allowed CORS origins
IS_HOSTED
—
Yes (prod)
true for hosted SaaS edition, false for self-hosted. Must be set explicitly — the API refuses to boot otherwise. Controls signup gating, billing, and email-verification policy.
TRUST_PROXY_HEADERS
—
Yes (prod)
true when behind a reverse proxy (Caddy, Cloudflare). Must be set explicitly in production.
TRUSTED_PROXY_CIDRS
—
When TRUST_PROXY_HEADERS=true
Comma-separated CIDRs of trusted reverse proxies (e.g., 10.0.0.0/8,172.16.0.0/12). Required when proxy headers are trusted.
TRUST_CF_CONNECTING_IP
false
Set to trueonly when Breeze is fronted by Cloudflare. Cloudflare overwrites the CF-Connecting-IP header at its edge, but a non-Cloudflare reverse proxy (including the bundled Caddy) does not strip it — so trusting it without a Cloudflare front would let a client spoof the header to defeat IP allowlists and per-IP rate limits. When off, client IPs are resolved from X-Forwarded-For instead. If you are behind Cloudflare and leave this unset, client-IP attribution (rate limits, audit logs, IP allowlists) falls back to X-Forwarded-For; the API logs a startup warning in that case.
IP_ALLOWLIST_ENFORCEMENT_MODE
enforce
Partner dashboard IP allowlist mode. Use off only as a break-glass switch if an allowlist locks everyone out. The allowlist only enforces when a partner has entries configured and proxy trust is working through TRUST_PROXY_HEADERS plus TRUSTED_PROXY_CIDRS.
Public origin for customer-portal links in outbound email (e.g. invoice emails). Falls back to PUBLIC_APP_URL if unset. The portal is served under /portal on the main domain.
PORTAL_BASE_PATH
/portal
Base path the customer portal is served under. Baked into the portal image at build time — changing it requires rebuilding the portal image and keeping the Caddy /portal route in sync.
Push notifications to the iOS app go directly through Apple’s push service (APNs). All four values are optional but all-or-none — setting any one makes the other three required, so a half-configured setup fails at startup rather than silently at first send. Leave them all unset to disable push.
Variable
Default
Description
APNS_AUTH_KEY
—
Contents of the Apple .p8 push auth key
APNS_KEY_ID
—
Apple push key ID
APNS_TEAM_ID
—
Apple developer team ID
APNS_BUNDLE_ID
—
iOS app bundle identifier
APNS_ENVIRONMENT
production
production or sandbox. An empty value is treated as unset.
Comma-separated raw base64 Ed25519 public keys that sign release manifests. Use the published default for official Breeze releases (it is a public key, safe to commit); only change it if you sign your own binaries. The API refuses to start in production without it. BREEZE_RELEASE_ARTIFACT_MANIFEST_PUBLIC_KEYS is accepted as an alias.
AGENT_AUTO_PROMOTE
true
Controls whether a newly registered/synced agent release immediately becomes the fleet upgrade target. true (default): publishing a release promotes it to the fleet at once. false: new binaries stay downloadable but the fleet only advances when you explicitly promote a version (platform admin + MFA) — see Controlled agent fleet rollout.
See Binary Distribution for details on local vs GitHub mode and S3 offloading.
The Breeze stack includes a coturn TURN server for WebRTC relay. Without TURN, remote desktop connections fail when either peer is behind symmetric NAT or a restrictive firewall.
Variable
Default
Required
Description
TURN_HOST
—
Yes (prod)
Public IP or hostname of the TURN server. Must be reachable by agents and viewers.
Both API and web Sentry integrations are off by default. Leave the DSN variables blank to disable. See Error Tracking & Privacy for what gets collected and how scrubbing works.
Variable
Default
Description
SENTRY_DSN
—
API Sentry DSN. Leave blank to disable server-side error tracking.
SENTRY_ENVIRONMENT
production
Sentry environment tag
SENTRY_RELEASE
—
Sentry release tag (e.g. git SHA)
SENTRY_TRACES_SAMPLE_RATE
0.1
Sentry performance trace sample rate (0.0-1.0)
SENTRY_PROFILES_SAMPLE_RATE
—
API performance-profiling sample rate (0.0-1.0). Leave blank to disable profiling.
PUBLIC_SENTRY_DSN_WEB
—
Web Sentry DSN. Leave blank to disable browser error tracking and on-error session replay. Inlined into the web bundle at build time.
SENTRY_AUTH_TOKEN
—
Sentry auth token used during the web build to upload source maps. If unset, source map upload is skipped and the build still succeeds.
Two rate-limit tiers protect the API. The generic per-user limit covers logged-in dashboard usage; the per-org agent limit caps how much traffic a single tenant’s fleet can generate, even with thousands of agents.
Variable
Default
Description
RATE_LIMIT_WINDOW_MS
60000
Sliding window duration (ms)
RATE_LIMIT_MAX_REQUESTS
100
Max requests per window
AGENT_ORG_RATE_LIMIT_PER_MIN
600
Per-organization sliding-window rate limit on agent-authenticated endpoints. Returns 429 with Retry-After: 60 when exceeded. Sized for ~5 active agents per org; raise for MSPs with very large fleets.
Maximum postgres-js connection pool size. Tune up if you see cascading 504s during heartbeat storms; confirm Postgres max_connections has headroom (default 100 is fine for a single API replica).
Portal state backend: memory or redis (auto redis in production)
PUBLIC_ENABLE_EDR_INTEGRATIONS
false
Surface the EDR UI: the /security/edr fleet page, the device Security-tab EDR panel, and the Security dashboard summary cards. Set true at web image build time to enable.
PUBLIC_ENABLE_NETWORK_DEVICES_IN_LIST
false
Surface network-discovered assets alongside agent endpoints in the Devices list view. Set true at web image build time to enable.
Azure AD app (client) ID for multi-tenant M365 backup. When set with the secret below, enables one-click admin consent flow for connecting Microsoft 365 backups.
C2C_M365_CLIENT_SECRET
—
Azure AD app client secret for multi-tenant M365 backup
These enable partners to connect a Microsoft 365 shared support mailbox so customer email becomes tickets and replies are sent from that mailbox. This is a separate Azure app registration from the Cloud-to-Cloud Backup app above.
Variable
Default
Description
TICKET_MAILBOX_M365_CLIENT_ID
—
Azure AD app (client) ID for the Breeze Ticketing app. When set with the secret, the Microsoft 365 mailbox card appears under Settings → Ticketing → Inbound Email.
TICKET_MAILBOX_M365_CLIENT_SECRET
—
Client secret for the Breeze Ticketing Azure app.
PUBLIC_TICKET_MAILBOX_APP_ID
—
Build-time, browser-exposed app id shown in the Application Access Policy PowerShell snippet on the settings card. Safe to publish – it is the app’s public client id.
Customer Microsoft 365 Graph-read consent (optional)
This feature is off by default and safe to leave unset — every variable below stays empty in a normal deployment. The API only reads and validates them when you turn the feature on. Concretely, the API refuses to boot only when M365_CUSTOMER_GRAPH_READ_ONBOARDING_ENABLED=trueand one of the required variables is missing or malformed; while the flag is off (or absent) these variables are never parsed and any values you set are ignored.
Customer Graph-read lets a customer administrator grant read-only Microsoft Graph consent to Breeze’s dedicated multi-tenant application without handing you a reusable secret. The Entra certificate and Key Vault access live entirely inside an isolated executor sidecar — the API holds only an opaque, version-pinned reference and a private key it uses to sign short-lived internal requests to that sidecar. See Customer Graph Read for the admin-facing side.
Variable
Required when enabled
Format / example
Meaning
M365_CUSTOMER_GRAPH_READ_ONBOARDING_ENABLED
— (the switch)
true / false (default false)
Master switch for new consent initiation only. Existing connection status, retest, and disconnect flows are independent of it. When false, none of the variables below are validated.
M365_CUSTOMER_GRAPH_READ_ONBOARDING_ORG_IDS
Yes
Literal * or comma-separated canonical org UUIDs (e.g. * or 1111...,2222...)
Which Breeze organizations may start consent. * = all organizations; otherwise an allowlist of canonical UUIDs. Boot fails if enabled and this is empty.
M365_CUSTOMER_GRAPH_READ_CLIENT_ID
Yes
Canonical UUID
Entra application (client) ID of the shared multi-tenant Graph-read app.
M365_CUSTOMER_GRAPH_READ_CREDENTIAL_VERSION
Yes
Exactly 32 lowercase hex characters
Pins which stored credential version the executor must use. Must equal the version segment of M365_CUSTOMER_GRAPH_READ_VAULT_REF.
Opaque Key Vault reference to the app’s certificate material. The trailing version segment must matchM365_CUSTOMER_GRAPH_READ_CREDENTIAL_VERSION, or boot fails.
M365_GRAPH_READ_EXECUTOR_URL
Yes
Origin-only HTTPS, e.g. https://m365-executor.internal (no path, query, fragment, or userinfo)
Private base URL of the executor sidecar. Anything other than a bare https://host[:port] origin is rejected at boot.
M365_GRAPH_READ_EXECUTOR_AUDIENCE
Yes
Literal m365-graph-read-executor
JWT audience for the API→executor internal auth token. Must equal that exact string.
M365_GRAPH_READ_EXECUTOR_SIGNING_KID
Yes
Non-empty string
Key ID of the API’s signing JWK. If the JWK file carries a kid, it must match this value.
M365_GRAPH_READ_EXECUTOR_SIGNING_PRIVATE_JWK_FILE
Yes
Absolute path to a file, e.g. /run/secrets/m365_graph_read_executor_signing_private_jwk
The API’s Ed25519 (kty: OKP, crv: Ed25519) private signing JWK. The file must be a regular file whose mode denies all group/other access (0600 or stricter) and should be mounted read-only. Boot fails on a symlink, a relative path, over-broad permissions, or a JWK that isn’t a valid 32-byte Ed25519 key. Do not put the private JWK inline in an env var.
Self-hosted only. Point the AI Agent at an Anthropic-compatible /v1/messages backend (vLLM 0.23+, LiteLLM). Must be an http(s) URL. Permitted only when IS_HOSTED is explicitly false (or 0/no/off); refused at boot if IS_HOSTED is true, unset, or invalid.
ANTHROPIC_AUTH_TOKEN
—
Bearer token for the custom backend (use instead of ANTHROPIC_API_KEY when ANTHROPIC_BASE_URL is set).
ANTHROPIC_MODEL
claude-sonnet-4-6
Override the default model id for a raw vLLM backend. Leave unset with a LiteLLM gateway that aliases claude-sonnet-4-6 → your model.
Self-hosted: alternative Anthropic-compatible backends
A self-hosted operator can run the AI Agent against any backend that speaks the
Anthropic /v1/messages dialect — keeping full tool-use and the aiGuardrails
stack — by setting:
Terminal window
IS_HOSTED=false# required — ANTHROPIC_BASE_URL is refused otherwise
ANTHROPIC_BASE_URL=http://localhost:8000# your vLLM / LiteLLM endpoint
ANTHROPIC_AUTH_TOKEN=<backendbearertoken>
# Raw vLLM only — match the served model id (skip with a LiteLLM alias):
ANTHROPIC_MODEL=<served-model-id>
The recommended route is a LiteLLM gateway that aliases claude-sonnet-4-6
to your backend model, so no ANTHROPIC_MODEL override is needed. For an
OpenAI-dialect-only endpoint, use the separate MCP_LLM_* openai-compatible
path instead (chat-only, no tool-use).
ANTHROPIC_BASE_URL is fail-closed: it is honored only when self-host is
affirmatively declared (IS_HOSTED explicitly false/0/no/off). If
IS_HOSTED is true, unset, or set to an unrecognized value, the API refuses
to boot when ANTHROPIC_BASE_URL is present, and the value is also stripped
from the SDK subprocess environment — so a stray or unmapped IS_HOSTED can
never redirect platform AI traffic to a third-party endpoint.
Cost tracking is best-effort. The SDK can’t price a non-Anthropic model id, so
it reports total_cost_usd: 0; Breeze then falls back to token-based pricing,
and an unrecognized model id is priced at conservative Opus-tier
DEFAULT_PRICING ($5/$25 per MTok) — i.e. likely an over-estimate for a
cheap local model, not $0. For accurate accounting, add the served model id to
MODEL_PRICING in aiCostTracker.ts, or use the openai-compatible path’s
MCP_LLM_PRICE_* overrides.
Optional NIST NVD API key. Raises NVD request rate limits so vulnerability feed syncs run faster. Without it, syncs use the slower public rate; the feature works either way.