Configuration
A self-hosted install is configured in infra/.env, which the compose files read. Copy
infra/.env.example and
fill it in. Each service gets only the secrets it uses.
Generate secrets with:
openssl rand -base64 32 # master key, action secret
openssl rand -hex 24 # passwords and the setup token: they go into URLs, keep them hex
Secrets
| Variable | Required | Used by | What |
|---|---|---|---|
BRAKE_MASTER_KEY |
Yes | API, proxy, worker, notifier | 32 bytes, base64. Encrypts provider keys and webhook URLs. Keep a copy off the host: without it they cannot be decrypted, and backups do not contain it. |
BRAKE_ACTION_SECRET |
Yes | API, notifier | Signs alert action links (Kill, Raise, Mute). |
BRAKE_SETUP_TOKEN |
Once | API | One-time token for brake init. Remove it after setup. |
VALKEY_PASSWORD |
Yes | Valkey and every service | Valkey’s password. |
POSTGRES_PASSWORD |
Yes | Postgres and every service | Postgres’s password (user and database are brake). |
URLs and sign-in
| Variable | Default | What |
|---|---|---|
BRAKE_PUBLIC_URL |
http://localhost:8082 |
Where browsers reach the notifier: alert action links point here. |
BRAKE_DASHBOARD_URL |
http://localhost:3000 |
Where the dashboard is: View links in alerts and the links in sign-in emails. |
BRAKE_PUBLIC_PROXY_URL |
http://localhost:8080 |
The proxy URL the dashboard shows in its setup steps. |
BRAKE_EMAIL_SIGN_IN |
false |
Sign in to the dashboard with emailed one-time links instead of API tokens. Needs SMTP_URL. |
BRAKE_PROXY_EGRESS_IPS |
The proxy’s outbound IP addresses, comma-separated (curl https://api64.ipify.org on the host). The dashboard tells people to restrict their provider keys to them. |
With the HTTPS overlay, BRAKE_PUBLIC_URL, BRAKE_DASHBOARD_URL and BRAKE_PUBLIC_PROXY_URL
are set from the two domain names below; you don’t set them yourself.
HTTPS (docker-compose.tls.yml)
| Variable | Example | What |
|---|---|---|
BRAKE_PROXY_DOMAIN |
brake.example.com |
The base URL apps and agents use instead of the provider’s |
BRAKE_APP_DOMAIN |
app.example.com |
Dashboard, admin API (/v1), alert links (/a/), SSO |
ACME_EMAIL |
ops@example.com |
Let’s Encrypt notices |
BRAKE_HTTP_PORT |
80 (default) |
Published HTTP port, if something else holds 80 |
BRAKE_HTTPS_PORT |
443 (default) |
Published HTTPS port, if something else holds 443 |
See Self-hosting: HTTPS.
Email, telemetry and price drift
| Variable | Default | What |
|---|---|---|
SMTP_URL |
Outgoing mail, e.g. smtps://user:password@smtp.example.com:465. Email alerts and sign-in links. |
|
BRAKE_EMAIL_FROM |
Spend Brake <alerts@localhost> |
The sender, on a domain your mail provider has verified. |
OPERATOR_EMAIL |
Where operator alerts (catalog price drift) go. | |
ALLOW_PRIVATE_COLLECTORS |
false |
Let orgs send OpenTelemetry spans to private addresses and a local http:// collector. Set on the API and the worker. Leave off on a server other people use. |
ALLOW_PRIVATE_CUSTOM_APIS |
false |
Let orgs’ custom APIs point at private and loopback addresses (an API on the same machine or network). Set on the API and the proxy. Leave off on a server other people use. |
GITHUB_TOKEN, GITHUB_REPO |
Optional: the worker opens a GitHub issue when a provider’s price page changes. |
Published images (docker-compose.release.yml)
Published images are coming; until then, build from source.
| Variable | What |
|---|---|
BRAKE_VERSION |
The image tag to run. Pin it; upgrade by changing it. |
BRAKE_API_IMAGE |
ee-api with the Business / Enterprise overlay |
BRAKE_WORKER_IMAGE |
ee-worker with the Business / Enterprise overlay |
Business / Enterprise (docker-compose.ee.yml)
| Variable | What |
|---|---|
BRAKE_PUBLIC_API_URL |
Where browsers reach the API (single sign-on) |
BRAKE_CATALOG_FEED_URL |
The signed catalog feed the worker fetches daily |
BRAKE_AUDIT_FILE |
Ship the audit log to a file (JSON Lines) |
BRAKE_AUDIT_S3_BUCKET, BRAKE_AUDIT_S3_PREFIX, BRAKE_AUDIT_S3_REGION |
Ship the audit log to S3 |
BRAKE_AUDIT_SYSLOG |
Ship the audit log to syslog (udp://host:514 or tcp://…) |
See ee/README.md.
Per-service settings
These are not in infra/.env; set them on a service with a compose override file if you need
them.
| Variable | Service | Default | What |
|---|---|---|---|
VALKEY_URL |
all | set by compose | redis-cluster://:password@host1:6379,host2:6379,… for a Valkey Cluster (rediss-cluster:// for TLS) |
DATABASE_URL |
API, worker, notifier | set by compose | Postgres connection string |
LOG_LEVEL |
all | info |
|
OTEL_EXPORTER_OTLP_ENDPOINT |
all | Export the services’ own OpenTelemetry traces (standard OTEL_* variables apply; secrets are stripped from every span) |
|
BRAKE_OTEL_CLIENT_URLS |
all | origin reduces outgoing request URLs in traces to scheme and host |
|
RATE_LIMIT_PER_MINUTE |
API | 600 |
Requests per token per minute (4x that per IP before authentication) |
GOOGLE_KEY_CHECK |
API | true |
Check a new Google key’s restrictions with a free call to Google |
TRUST_PROXY |
proxy, API | false |
Use X-Forwarded-For for client IPs (the HTTPS overlay sets it) |
UPSTREAM_TIMEOUT_MS |
proxy | 30000 |
How long the proxy waits for a provider |
BRAKE_SERPAPI_PLAN |
proxy | starter |
The SerpApi plan whose per-search price applies |
BRAKE_SERPAPI_SPEED |
proxy | normal |
SerpApi speed mode: ludicrous bills 2x, ludicrous_max 4x |
BRAKE_GENERIC_PROVIDERS |
proxy | JSON array of generic adapter configs: price any other API for every org (see below) | |
BRAKE_CUSTOM_API_MAX_RESPONSE_BYTES |
proxy | 10485760 |
Largest custom API response passed on; a longer one is cut and recorded as an error |
BRAKE_CATALOG_DIR |
proxy | Updated catalog data (current/), checked every 30 s |
|
METRICS_PORT |
proxy, worker | 9465, 9464 |
Prometheus metrics port |
BRAKE_ACTION_TTL_SEC |
notifier | 900 |
How long alert action links work |
BRAKE_MAX_CUSTOM_RAISE_USD |
notifier | 500 |
The largest custom raise an alert link offers |
ALLOW_PRIVATE_WEBHOOKS |
notifier | false |
Allow webhook hosts that resolve to private addresses |
The service READMEs list the rest: proxy, API, worker, notifier.
Generic providers
Orgs can add APIs without a built-in adapter themselves, from the dashboard, the API or the CLI:
see Custom APIs. Those belong to one org and are served at
<proxy>/custom/<id>/....
BRAKE_GENERIC_PROVIDERS on the proxy does the same for every org on the install, with a path
prefix of your choice. It is the operator’s list: it can use http:// origins and private
addresses, which custom APIs cannot. Each entry is a custom API definition plus a prefix:
[
{
"id": "acme",
"prefix": "/acme",
"origin": "https://api.acme.example",
"auth": { "type": "header", "name": "Authorization", "valuePrefix": "Bearer " },
"routes": [
{
"method": "POST",
"path": "/v1/search",
"sku": "search",
"pricing": { "model": "perRequest", "usd": "0.005" }
},
{
"path": "/v1/agents/**",
"sku": "agent_run",
"pricing": {
"model": "reported",
"header": "X-Credits-Used",
"usdPerUnit": "0.001",
"maxPerRequest": "0.50"
}
}
]
}
]
Routes are priced the same two ways as custom APIs: perRequest (a flat price) or reported
(the cost the API reports in a response header or JSON field, with a maximum reserved up front);
Custom APIs: pricing explains both. The older spelling
"pricePerRequest": "0.005" still works and means perRequest.
Store the API’s key as a provider credential for acme (POST /v1/providers/credentials with
"provider": "acme"), create a virtual key that uses it
(brake keys create --name app --credential acme=<credential id>), and call
<proxy>/acme/v1/search with the virtual key in X-Brake-Key. Route paths are globs: *
matches one segment, ** any number. Only 2xx responses are billed unless "billed": "all".
The prefix /custom is reserved for custom APIs. Give operator providers ids no org will use for
a custom API: a key binds one credential per provider id.