Skip to content

Hub configuration

The Hub reads a YAML config file at startup from BRITTLE_CONFIG_PATH (default: /etc/brittle/hub.config.yaml). Most deployments only need the four secrets: DATABASE_URL, JWT_SECRET, BRITTLE_AI_SECRET_KEY, and whatever your artifact store needs.

This page lists the full schema. The Installation page ships a working config inline; reach for individual fields here only when you need to deviate.

server:
host: 0.0.0.0
port: ${HUB_PORT:3100}
logLevel: ${LOG_LEVEL:info}
database:
url: ${DATABASE_URL}
auth:
jwtSecret: ${JWT_SECRET}
artifacts:
store: local:/var/lib/brittle/artifacts
# Required-by-schema blocks even when their fields all default.
session: {}
nodes:
heartbeat: {}
internal: {}
tunnel: {}
ai:
enabled: false

${VAR} and ${VAR:default} placeholders interpolate from process env at startup.

FieldTypeDefaultNotes
hoststring0.0.0.0Bind address. Leave as-is unless you’re binding to a specific NIC.
portint3000HTTP port the Hub listens on inside the container.
logLevelenuminfoOne of `fatal
FieldTypeRequiredNotes
urlstringyesPostgres URL: postgresql://user:pass@host:port/db?schema=public.
FieldTypeRequiredNotes
jwtSecretstringyesMin 32 chars. Used to sign session cookies. Rotate by replacing the value and restarting.

Where session artifact bytes (videos, traces, screenshots) live. The store field is a URI:

PrefixBehaviour
local:Filesystem under the absolute path. The Hub streams bytes through itself for upload + download.
s3://S3-compatible bucket. Reporters and the dashboard hit the bucket directly; the Hub doesn’t proxy bytes.
artifacts:
store: s3://my-brittle-artifacts/prod
s3:
region: us-east-1
accessKeyId: ${AWS_ACCESS_KEY_ID}
secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
# Optional. Set for non-AWS providers (MinIO, R2, GCS S3-compat).
endpoint: https://example.r2.cloudflarestorage.com
forcePathStyle: true
# Optional. When minting download URLs, use this hostname instead
# of `endpoint`. Lets internal traffic stay on the private endpoint
# while browsers/CI runners hit a public CDN URL.
publicEndpoint: https://cdn.example.com
presignTtlSeconds: 900

The s3: sub-block applies only when store: s3://.... Inner fields are individually optional. Set the ones your provider needs.

Optional. AI failure pattern detection across runs.

ai:
enabled: true
provider: gemini
FieldTypeNotes
enabledboolMaster switch. When false (default), no AI jobs run regardless of per-org configuration.
providerenumOne of `openai
apiKeystringDefault API key. Per-org keys configured in the dashboard override this. Optional.
modelstringDefault model. Per-org override available. Optional.

Per-organisation provider config (via dashboard Manage → Organization → AI) overrides this hub-level block at job-execution time. Hub-level config is the default for orgs without their own setup.

Reserved for monitoring endpoints (queue depth, AI job status). Set internal.token to a bearer string to enable them; leave unset and the endpoints return 503. See Production notes.

The schema also includes a few placeholder blocks (session, nodes, tunnel) that don’t do anything today. Leave them empty or omit them entirely.

In addition to placeholders interpolated by the YAML config, the Hub reads these directly from process env:

VariableRequiredNotes
BRITTLE_CONFIG_PATHnoPath to the YAML config. Default /etc/brittle/hub.config.yaml.
BRITTLE_AI_SECRET_KEYyesMin 16 chars. Encrypts per-org AI keys in the database. Generate with openssl rand -hex 32. Required even when AI is off.
BRITTLE_RUN_MIGRATIONSnoDefault true. Set to false to skip the DB migration step at startup (use when you migrate out of band).
NODE_ENVnoDefault production. Affects logger output format.

All of the YAML config’s ${VAR} placeholders also read from env.

There’s no SIGHUP reload. After editing the config, restart the Hub:

Terminal window
docker compose restart brittle