Reference for every environment variable used by the trial bundle and self-hosted deployments (brutor-trial-service/trial_bundle/.env). Setup walkthrough: Docker Compose install and Configuration.
Image tag for all Brutor services in the compose file. Deliberately not pinned to a literal here: a stale example gets copy-pasted into a .env, and an image tag that predates the current schema will not serve a database the Control Plane has already migrated.
Full connection URL when running the planes outside compose
The Control Plane owns the schema and runs Alembic migrations automatically on startup (with an advisory lock, so concurrent instances don’t race). The Core Proxy never alters the schema — upgrades are pull-new-images-and-restart.
Comma-separated allowed origins. Read by both the control plane and the core proxy. On the core proxy it governs the portal API, health, version and docs; the protocol routes (/v1/proxy/*, /v1/messages, /v1beta/*, /a2a/*, /.well-known/*) answer any origin. List the address you open the portal at.
GLOBAL_KEY_ALLOWED_TARGET_TENANTS
No
(unset = never)
Comma-separated tenant ids a global-scope API key may act for when it sends X-Tenant-ID for a tenant other than its own. Unset means a global key is always attributed to its owning tenant.
Tenant admins configure URLs the Core Proxy and the KB connector runtime then
fetch with their own network position: MCP servers, LLM endpoints, A2A agents,
IdP discovery, connector sources. The policy is applied three times with one
rule set — when a URL is saved (Control Plane, HTTP 400 with the reason),
and when it is fetched (Core Proxy and connector sync, after DNS
resolution, so a public name that resolves to a private address is refused
too). Link-local addresses (169.254/16, fe80::/10 — where every cloud
metadata service lives), the IPv6 metadata address and reserved addresses are
always refused. Set both variables on the Core Proxy, the Control Plane and
the KB connector sync service.
Variable
Required
Default / example
Description
BRUTOR_OUTBOUND_DENY_PRIVATE
No
false
Also refuse loopback, RFC 1918, carrier-grade NAT (100.64/10) and IPv6 unique-local destinations. Turn on for multi-tenant deployments. Off by default so a self-hosted stack that runs Ollama or an MCP server next to the proxy keeps working.
BRUTOR_OUTBOUND_ALLOW_HOSTS
No
ollama,skill-runner,*.svc.cluster.local
Comma-separated hostnames that bypass the address checks. *.suffix matches a domain. List your own internal services here when BRUTOR_OUTBOUND_DENY_PRIVATE is on.
Deployment fallback judge model for semantic policies. Used only when the policy pins no judge and the tenant has no default. With none of the three set, the policy has no judge and fails open.
Model a newly-provisioned tenant gets as default_judge_model_id. Override in Settings → Tenant → Default models.
KB_POLL_INTERVAL_SECS
No
5
Job-queue poll interval
KB_MAX_CONCURRENT_JOBS
No
4
Concurrent ingestion jobs
KB_CHUNK_SIZE_TOKENS
No
500
Chunk size
KB_CHUNK_OVERLAP_TOKENS
No
50
Chunk overlap
KB_EMBEDDING_BATCH_SIZE
No
50
Texts per embedding request
KB_EMBEDDING_CONCURRENCY
No
4
Parallel embedding requests
STALE_PROCESSING_SECS
No
1800
A document left in processing longer than this (the uploader died or was restarted mid-batch) is claimed again instead of staying stuck until an operator reprocesses it. Set on the uploader container.
MAX_DOCUMENT_BYTES
No
52428800 (50 MiB)
Largest raw document the uploader reads into memory; larger rows are marked failed with a clear message. Office parts (.docx/.pptx XML) are additionally refused above 64 MiB inflated, which stops zip bombs. Set on the uploader container.
PROXY_SERVICE_API_KEY
Yes
(the PROXY_HEALTH_CHECK_API_KEY value)
Global-scope key the uploader uses to embed through the Core Proxy. The same key gates the uploader’s /api/v1/jobs listing API (X-API-Key or Authorization: Bearer); when unset that API answers 503.
KB_CONNECTOR_SYNC_MAX_RUN_SECS
No
3600
Connector run lease. A connector still marked running whose last_run_started_at is older than this is treated as abandoned and claimed again.
KB_CONNECTOR_SYNC_SHUTDOWN_GRACE_SECS
No
30
On SIGTERM the connector scheduler stops claiming and waits this long for in-flight syncs; anything still running is aborted and rescheduled for immediate retry.
Per-call JWT lifetime. Set on the Core Proxy, which mints the token
SKILL_RUNNER_JWT_LEEWAY_SECS
No
30
Clock skew the runner tolerates when validating that token
SKILL_RUNNER_SANDBOX_TIMEOUT_SECS
No
30
Sandboxed execution timeout
SKILL_RUNNER_TRUSTED_TIMEOUT_SECS
No
120
Trusted execution timeout
SKILL_RUNNER_MAX_REQUEST_BYTES
No
16777216
Max request size to the runner (16 MB)
SKILL_RUNNER_WORKSPACE_ROOT
No
system temp dir
Where per-run skill workspaces are created
SKILL_RUNNER_WORKSPACE_TTL_SECS
No
3600
How long a workspace survives before it is reaped
SKILL_RUNNER_LISTEN_ADDR
No
0.0.0.0:8210
Runner bind address
SKILL_RUNNER_TRUSTED_ENV_ALLOWLIST
No
(empty)
Comma-separated env vars a trusted skill may read
The three timeout/size variables carry the SKILL_RUNNER_ prefix in the
code (brutor-skill-runner/src/config.rs) and in the shipped compose file.
They were previously documented here without it, so an operator who set
SANDBOX_TIMEOUT_SECS=300 to lengthen a slow skill got no effect and no
warning — the runner kept the 30-second default and killed the skill.
Read by the Core Proxy only. All optional; the defaults are what the trial
bundle runs with.
Variable
Default
Description
PROXY_LISTEN_ADDR
0.0.0.0:8100
Bind address
LLM_TIMEOUT_SECS
120
Default upstream timeout for LLM calls (per-model overrides apply)
MCP_TIMEOUT_SECS
30
Default upstream timeout for MCP calls
MCP_REQUEST_STATE_MAX_BYTES
65536
Largest per-request MCP state the proxy keeps in memory
A2A_DELEGATION_MAX_AGE_SECS
300
Maximum age of a delegation token accepted on A2A inbound
SESSION_CLEANUP_INTERVAL_SECS
300
How often expired in-memory sessions are swept
GUARDRAIL_CACHE_TTL_SECS
60
Guardrail-configuration cache lifetime
MODEL_METRICS_FLUSH_INTERVAL_SECS
5
Interval for the batched llm_model metrics writer (0 = 100 ms, test only)
EXPECTED_CONCURRENT_REQUESTS
(unset)
When set, sizes the DB pool as per-request budget × this ceiling + BACKGROUND_CONNECTION_OVERHEAD; otherwise DB_POOL_SIZE applies
BACKGROUND_CONNECTION_OVERHEAD
10
Connections reserved for background loops when EXPECTED_CONCURRENT_REQUESTS is set
BRUTOR_DIAGNOSTIC_POOL_ENABLED
true
Tiny dedicated DB pool for slow-request captures and pool gauges
BRUTOR_DIAGNOSTIC_POOL_SIZE
2
Size of that pool
BRUTOR_POOL_SAMPLER_INTERVAL_SECS
15
Interval for the proxy_db_pool_* gauges
POOL_SATURATION_SUSTAINED_SECS
60
Seconds of idle == 0 before a pool-saturation system alert fires
POOL_SATURATION_P95_THRESHOLD_SECS
(see code)
p95 acquire latency that counts as saturation
BRUTOR_REPLICA_ID
(hostname)
Replica label in cache-stats fan-out and logs
METRICS_ENABLED
false
Serve Prometheus metrics on /metrics, on the separate METRICS_LISTEN_ADDR listener (never on the proxy port). Unauthenticated: do not publish that port. Labels carry provider, model, surface and status, not tenant or user ids.
METRICS_LISTEN_ADDR
0.0.0.0:9464
Address of the metrics listener; point Prometheus at it
OTEL_EXPORTER_OTLP_ENDPOINT
(unset = off)
OTLP/gRPC trace export target
OTEL_SERVICE_NAME
brutor-gateway-core
Service name on exported traces
KNOWLEDGE_BASE_ENABLED
true
Enable RAG retrieval on the LLM surface
KB_DEFAULT_TOP_K
5
Default chunks retrieved per query
KB_DEFAULT_SIMILARITY_THRESHOLD
0.75
Default minimum similarity
KB_DEFAULT_MAX_CONTEXT_TOKENS
2000
Default context budget for retrieved chunks
QDRANT_URL
(unset)
Qdrant gRPC endpoint (:6334); required when the semantic cache or the knowledge base is enabled
BRUTOR_RERANKER_URL
(unset = off)
Optional reranker endpoint applied to retrieved chunks
BRUTOR_RERANKER_MODEL
rerank-english-v3.0
Reranker model name
BRUTOR_RERANKER_API_KEY
(unset)
Reranker credential
BRUTOR_RERANKER_TOP_K
(see code)
Chunks kept after reranking
MEDIA_S3_REGION
us-east-1
Region for the S3 media backend
MEDIA_S3_PUBLIC_ENDPOINT
(unset)
Endpoint used when signing URLs handed to browsers, when it differs from MEDIA_S3_ENDPOINT
Audit rows are always hash-chained, which detects modification. A signing key
additionally lets the proxy attest to each batch, which is what detects a wholesale
rewrite. Without one, System → Audit Chains reports unattested rather than
verified — an honest result, not a broken one.
Variable
Required
Default / example
Description
BRUTOR_AUDIT_SIGNING_KEY
No
openssl rand -hex 32
Ed25519 seed (32 bytes, hex) the Core Proxy signs audit checkpoints with. Unset = chained but unattested.
BRUTOR_AUDIT_TRUSTED_KEYS
No
<hex>,<hex>
Public keys whose signatures are still accepted, comma-separated. The current key is trusted implicitly; this is only needed for rotation.
Rotation is additive, so no migration and no re-signing:
Append the old public key to BRUTOR_AUDIT_TRUSTED_KEYS.
Set BRUTOR_AUDIT_SIGNING_KEY to the new seed and restart.
New batches are signed with the new key; everything the old key signed keeps
verifying, because its public half is stored on the checkpoint and still trusted.
Never remove a public key that signed checkpoints you still retain. Doing so reports
that history as forged — which is the alarm you want if the key really was
compromised, and a false one if it was merely retired.
Sealed action records and witnessed tenant logs (compliance evidence programme).
All on the Core Proxy, except the local-witness variables, which the Control Plane
reads. A separate key from the audit key, on purpose. Details:
Signed statements & keys.
Variable
Required
Default / example
Description
BRUTOR_EVIDENCE_SIGNING_KEY
No
openssl rand -hex 32
Ed25519 seed of the deployment evidence key. Unset or invalid = no records are sealed; traffic unaffected
BRUTOR_EVIDENCE_RETIRED_KEYS
No
<hex>,<hex>
Public keys of retired deployment evidence keys, still trusted for verification
BRUTOR_DEPLOYMENT_ID
No
default / prod-eu-1
Hashed into every tenant’s log id and the default issuer. Set once; never change it on an existing database
Level enforced when the governance database is unreachable and no recent level is cached. restricted keeps reads and completions serving while refusing anything that changes the world. Set suspended for a total stop.
The proxy never falls back to autonomous. An operator who suspends a runaway agent
must not have that suspension undone by an unrelated database blip — which is exactly
the moment the control is being relied on.
Service-provider keypair for SAML SSO and the post-login path
A2A_SAMPLE_PUBLIC_URL
(see code)
Public URL stamped on the seeded sample agent card
APP_VERSION
dev
Reported by /v1/version; set by the release build
BRUTOR_GITHUB_TOKEN / GITHUB_TOKEN
(unset)
GitHub token used by the skill-source sync scheduler when it walks a skills repository (Agent Skills → Sources). Optional: without it the unauthenticated 60 requests/hour budget is too small for a full walk of a large repo such as Anthropic’s skills, and that source is skipped with a warning. A fine-grained personal access token with Contents: read-only on the target repositories lifts the limit to 5,000/hour
Both consoles ship a PostHog client that is off unless POSTHOG_KEY is set
(POSTHOG_HOST defaults to https://eu.i.posthog.com). When enabled, the
consoles identify the signed-in user with their id, email and tenant id
and send page/feature events to that host. Treat enabling it as a
data-processing decision for your deployment; there is no in-product opt-out.