Skip to content

Environment Variables

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.

Variable Required Default / example Description
BRUTOR_LICENSE_KEY Yes BRUTOR-V1-... License key. The Core Proxy refuses to start without it.
JWT_SECRET Yes openssl rand -hex 32 HS256 signing secret for gateway/portal JWTs. Shared Core Proxy + Control Plane.
ENCRYPTION_KEY Yes python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" Fernet key encrypting stored secrets. Shared across both planes.
QDRANT_JWT_SIGNING_SECRET Yes openssl rand -hex 32 Signs the short-lived tenant-scoped Qdrant JWTs. Shared Core Proxy ↔ Qdrant.
SKILL_RUNNER_JWT_SECRET Yes openssl rand -hex 32 Authenticates Core Proxy → Skill Runner calls. Must match on both services.
PROXY_HEALTH_CHECK_API_KEY Yes sk_brutor_api_<43 url-safe chars> API key used by internal health checks against the proxy.
SYSTEM_ADMIN_PASSWORD Yes Admin123! Seeded system-admin password. Change in production.
DEFAULT_TENANT_ADMIN_PASSWORD Yes Admin123! Seeded tenant-admin password. Change in production.
BRUTOR_VERSION Yes (compose) see release notes 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.

Database (PostgreSQL — shared by both planes)

Section titled “Database (PostgreSQL — shared by both planes)”
Variable Required Default / example Description
DB_HOST Yes postgres Database host
DB_PORT No 5432 Database port (also the host-mapped port)
DB_USER Yes brutor Database user
DB_PASSWORD Yes brutor123 Database password
DB_NAME Yes ai_gateway Database name
DB_POOL_SIZE No 20 Connection pool size
DATABASE_URL Yes (native runs) postgresql://brutor:brutor123@localhost:5432/ai_gateway 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.

Variable Required Default / example Description
REDIS_URL Yes redis://redis:6379/0 Cache, rate-limit counters, pub/sub
REDIS_DISABLED No false true = in-memory fallback — single-pod only
REDIS_PORT No 6379 Host-mapped Redis port (compose)
Variable Required Default Description
PROXY_PORT No 8100 Core Proxy (runtime + Portal API)
GATEWAY_PORT No 5050 Control Plane (admin API)
ADMIN_UI_PORT No 3002 Admin Console
PORTAL_PORT No 3001 User Portal
QDRANT_REST_PORT No 6333 Qdrant REST
QDRANT_GRPC_PORT No 6334 Qdrant gRPC
MINIO_API_PORT No 9000 MinIO S3 API
MINIO_CONSOLE_PORT No 9001 MinIO console
Variable Required Default / example Description
JWT_EXPIRES_IN No 3600 JWT lifetime in seconds
JWT_ALGORITHM No HS256 JWT signing algorithm
CORS_ALLOW_ALL No false Allow all origins (dev only)
CORS_ORIGINS No http://localhost:3001,http://localhost:3002 Comma-separated allowed origins
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.
Variable Required Default / example Description
SEMANTIC_CACHE_ENABLED No true Enable the LLM semantic cache
EMBEDDING_MODEL No text-embedding-3-small Model used to embed prompts for similarity lookup
JUDGE_MODEL No (unset) 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.
SIMILARITY_THRESHOLD No 0.95 Minimum cosine similarity for a semantic hit
SEMANTIC_CACHE_EXACT_TTL_SECS No 3600 Exact-match entry TTL
SEMANTIC_CACHE_SEMANTIC_TTL_SECS No 86400 Semantic-match entry TTL
SEMANTIC_CACHE_TIME_BUCKET No daily Time-bucketing for cache keys
Variable Required Default / example Description
DEFAULT_EMBEDDING_MODEL No text-embedding-3-small Default embedding model for KB collections
DEFAULT_JUDGE_MODEL No gpt-5-mini 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.
Variable Required Default / example Description
MEDIA_STORAGE_BACKEND No s3 Storage backend
MEDIA_BUCKET No brutor-media Bucket name
MEDIA_S3_ENDPOINT No http://minio:9000 S3 endpoint (MinIO in the bundle)
MEDIA_S3_ACCESS_KEY_ID Yes (s3) minioadmin S3 access key
MEDIA_S3_SECRET_ACCESS_KEY Yes (s3) minioadmin S3 secret key
MEDIA_S3_FORCE_PATH_STYLE No true Path-style addressing (required for MinIO)
MEDIA_SIGNED_URL_TTL_SECS No 900 Signed URL lifetime
MEDIA_GC_INTERVAL_SECS No 21600 Expired-media garbage-collection interval
Variable Required Default / example Description
SKILL_RUNNER_URL No http://skill-runner:8210 Internal Skill Runner address (Core Proxy side)
SKILL_RUNNER_JWT_SECRET Yes openssl rand -hex 32 Shared Core Proxy ↔ Skill Runner
SKILL_RUNNER_JWT_TTL_SECS No 300 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.

Variable Required Default / example Description
KEY_PROVIDER No local local | aws_kms | vault
KMS_KEY_ID If aws_kms arn:aws:kms:... AWS KMS key
AWS_REGION If aws_kms eu-west-1 AWS region
AWS_KMS_ENDPOINT_URL No http://localstack:4566 Override for LocalStack (compose kms profile)
VAULT_ADDR If vault http://vault:8200 Vault address
VAULT_TOKEN If vault ... Vault token
VAULT_TRANSIT_KEY If vault brutor Transit key name
VAULT_TRANSIT_MOUNT If vault transit Transit mount path

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 Expose Prometheus metrics on /metrics (unauthenticated; keep it on the internal network — label sets include tenant ids)
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
GLOBAL_KEY_ALLOWED_TARGET_TENANTS (unset) See Auth and CORS

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:

  1. Append the old public key to BRUTOR_AUDIT_TRUSTED_KEYS.
  2. Set BRUTOR_AUDIT_SIGNING_KEY to the new seed and restart.
  3. 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.

Variable Required Default / example Description
BRUTOR_AUTONOMY_FALLBACK_FLOOR No restricted 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.

Control Plane — database, deploy and provisioning

Section titled “Control Plane — database, deploy and provisioning”

Read by the Control Plane only.

Variable Default Description
GUNICORN_WORKERS 2 Uvicorn workers under Gunicorn (GUNICORN_TIMEOUT, GUNICORN_MAX_REQUESTS, GUNICORN_BACKLOG as in gunicorn.conf.py)
DB_POOL_SIZE / DB_MAX_OVERFLOW 20 / 40 SQLAlchemy pool sizing
DB_POOL_TIMEOUT / DB_POOL_RECYCLE / DB_POOL_PRE_PING 30 / 3600 / true Pool wait, recycle age, liveness ping
DB_CONNECT_TIMEOUT / DB_COMMAND_TIMEOUT 10 / 60 Connect and statement timeouts (seconds)
DB_SSL_MODE prefer libpq sslmode; DB_SSL_ROOT_CERT, DB_SSL_CERT, DB_SSL_KEY for mutual TLS
DB_ISOLATION_LEVEL (driver default) SQLAlchemy isolation level
DB_ECHO / DB_ECHO_POOL false SQL / pool logging
MULTI_TENANCY_ENABLED true Single-tenant installs fall back to the default tenant
DEFAULT_TENANT_ID default Id of the seeded default tenant
DEFAULT_TENANT_ADMIN_USERNAME / DEFAULT_TENANT_ADMIN_EMAIL admin / admin@example.com Seeded tenant admin (password: DEFAULT_TENANT_ADMIN_PASSWORD)
SYSTEM_ADMIN_USERNAME / SYSTEM_ADMIN_EMAIL sysadmin / sysadmin@system.local Seeded system admin (password: SYSTEM_ADMIN_PASSWORD)
TRIAL_USER_USERNAME / TRIAL_USER_PASSWORD / TRIAL_USER_EMAIL / TRIAL_USER_DISPLAY_NAME trial / Trial123! / … Seeded portal end user for the trial bundle
PROXY_INTERNAL_URL http://core:8100 Internal Core Proxy address for model health checks, audit verification and assurance-check compilation
PORTAL_PUBLIC_URL / GATEWAY_BASE_URL (unset) Public URLs used in emails and generated links
QDRANT_HTTP_URL (derived from QDRANT_URL, :6333) Qdrant REST endpoint for collection management
OLLAMA_BASE_URL (unset) Local Ollama endpoint offered when seeding models
RESEND_API_KEY / RESEND_FROM_EMAIL (unset) / noreply@brutor.ai Transactional email (invitations, digests). Unset = emails are logged, not sent
SAML_SP_CERTIFICATE / SAML_SP_PRIVATE_KEY / SAML_DEFAULT_RETURN_PATH (unset) / / 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
BRUTOR_MCP_DEPLOY_NETWORK / BRUTOR_MCP_DEPLOY_HOST_PORT_RANGE (unset) Docker network and host-port range for MCP servers deployed from the console
KUBECONFIG / BRUTOR_K8S_NAMESPACE_TEMPLATE / BRUTOR_K8S_SERVICE_TYPE / BRUTOR_K8S_DEPLOY_TIMEOUT_SECS (see code) Kubernetes deployment target for MCP servers
BRUTOR_ECS_CLUSTER_NAME / BRUTOR_ECS_SUBNETS / BRUTOR_ECS_SECURITY_GROUPS / BRUTOR_ECS_EXECUTION_ROLE_ARN / BRUTOR_ECS_TASK_ROLE_ARN / BRUTOR_ECS_LOG_GROUP / BRUTOR_ECS_CPU / BRUTOR_ECS_MEMORY / BRUTOR_ECS_DEPLOY_TIMEOUT_SECS /brutor/mcp, 512, 1024, 120 ECS deployment target for MCP servers (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION or the instance role)
SYSADMIN_PASSWORD_SECRET_ARN / SYSADMIN_PASSWORD_ROTATION_ECS_CLUSTER / SYSADMIN_PASSWORD_ROTATION_ECS_SERVICES (unset) Rotate the system-admin password into Secrets Manager and redeploy the named services
GOOGLE_APPLICATION_CREDENTIALS (unset) Service-account file for Vertex/Google integrations
Variable Default Description
HOST / PORT 0.0.0.0 / 5001 Bind address
JWT_SECRET (required) Shared platform secret; the registry refuses to start with a placeholder value
REGISTRY_DEV_MODE false Accept any bearer token as demo_user when JWT_SECRET is unset — throwaway stacks only
CORS_ALLOW_ALL false Permissive CORS (dev only)
AGGREGATOR_ENABLED / AGGREGATOR_INTERVAL_SECONDS / AGGREGATOR_FULL_SYNC_HOURS (see code) Federation sync from upstream registries
GOVERNANCE_ENABLED / GOVERNANCE_API_BASE_URL / GOVERNANCE_TENANT_ID / GOVERNANCE_HTTP_TIMEOUT_SECONDS / GOVERNANCE_CACHE_TTL_SECONDS false / http://control-plane:5050 / default / 5 / … Governance enrichment from the Control Plane (GOVERNANCE_API_TOKEN is minted per call; no admin credential lives in the container)
REGISTRY_SSRF_ALLOW_HOSTS (unset) Hosts the namespace-verification fetch may reach even if private
SERVER_JSON_SCHEMA_URL (bundled) Override for the server.json schema
Variable Default Description
KB_CONNECTOR_SYNC_LISTEN_ADDR 0.0.0.0:8200 Health/metrics bind address
KB_CONNECTOR_SYNC_POLL_INTERVAL_SECS / KB_CONNECTOR_SYNC_MAX_CONCURRENT 15 / 4 Scheduler tick and parallel syncs (lease and grace: see Knowledgebase)
CONFLUENCE_API_OVERRIDE, NOTION_API_OVERRIDE, GOOGLE_DRIVE_API_OVERRIDE, SLACK_API_OVERRIDE, GITHUB_API_OVERRIDE, JIRA_API_OVERRIDE, SHAREPOINT_API_OVERRIDE, SHAREPOINT_AUTH_OVERRIDE (vendor API) Point a connector at a mock or a private API gateway (tests, air-gapped installs)
Variable Required Default / example Description
LOG_LEVEL No info Control Plane log level
USE_JSON_LOGGING No true Structured JSON logs (Control Plane)
RUST_LOG No brutor_gateway_core=info,tower_http=info Core Proxy log filter
Variable Required Default / example Description
BRUTOR_DEPLOYMENT_MODE No self_hosted self_hosted | saas | development
MCP_DEPLOYMENT_DRIVER No docker Driver for gateway-deployed MCP servers: docker | k8s | ecs | none
BRUTOR_SHARED_MCP_SERVERS No [{...}] JSON array of MCP servers auto-registered at startup
Variable Required Default / example Description
VITE_GATEWAY_URL (Admin UI) Yes http://localhost:5050 Admin UI → Control Plane
VITE_GATEWAY_URL (Portal) Yes http://localhost:8100 User Portal → Core Proxy (the portal never talks to the Control Plane)
VITE_TENANT_ID (Portal) Yes default Tenant the portal serves

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.