Skip to content

Public Environment Variables (Operator-Tunable Allowlist)

Operators may set these env vars in production. Everything else with a BALDUR_* prefix is advanced / internal and subject to change in v1.x.

The full settings inventory is internal to v1.0; operator-tunable promotion happens via dedicated proposals in later releases.

(PRO) marker

Entries tagged (PRO) require the baldur_pro package — the backing service ships only in baldur_pro, so without it the knob is a silent no-op.

Resilience core

BALDUR_CB_FAILURE_THRESHOLD=5           # consecutive failures that trip the breaker
BALDUR_CB_FAILURE_RATE_THRESHOLD=50.0   # failure % over the recent-call window that also trips it; 0 disables the rate trigger
BALDUR_CB_SLIDING_WINDOW_SIZE=100       # recent calls the failure rate is measured over, per worker process
BALDUR_CB_MINIMUM_CALLS=10              # calls the window needs before the rate is trusted; gates the rate trigger only
BALDUR_CB_RECOVERY_TIMEOUT=60
BALDUR_CB_HALF_OPEN_MAX_CALLS=3
BALDUR_RETRY_MAX_ATTEMPTS=3
BALDUR_RETRY_BACKOFF_STRATEGY=exponential  # exponential | linear | constant | decorrelated_jitter
BALDUR_RETRY_BASE_DELAY=1.0
BALDUR_RETRY_MAX_ELAPSED=30.0  # total wall-clock retry budget (s); unset = no budget. Distinct from the per-sleep max_delay cap.
BALDUR_IDEMPOTENCY_ENABLED=true
BALDUR_IDEMPOTENCY_DEFAULT_CACHE_TTL=60
BALDUR_IDEMPOTENCY_GATE_MEMORY_TTL_SECONDS=1800

DLQ

Dead-letter capture ships in the OSS core: a failed operation is recorded with the context needed to replay it, size limits plus the overflow strategy bound the queue, and the non-blocking outbox keeps capture off the request hot path.

BALDUR_DLQ_ENABLED=true
BALDUR_DLQ_MAX_SIZE=100000
BALDUR_DLQ_OUTBOX_ENABLED=true

Replay automation

Automatic replay on circuit-breaker recovery. ON_RECOVERY_ENABLED is on by default; setting it to false disables the on-recovery dispatch and, with it, the per-recovery WARNING about a missing replay worker. SERVICE_FAILURE_TYPE_MAP maps each recovered service to the failure types whose captured entries it is responsible for — an empty mapping leaves the loop unable to select entries on recovery (surfaced as a blocked-with-signal event, not a silent no-op). See DLQ + Replay → Closing the loop for the full set of prerequisites.

BALDUR_REPLAY_AUTOMATION_ON_RECOVERY_ENABLED=true
BALDUR_REPLAY_AUTOMATION_ON_RECOVERY_MAX_ITEMS=100
# JSON object: {"service_name": ["FAILURE_TYPE", ...]}
BALDUR_REPLAY_AUTOMATION_SERVICE_FAILURE_TYPE_MAP='{"payment_api": ["TIMEOUT", "CONNECTION_ERROR"]}'

Audit

BALDUR_AUDIT_ENABLED=true

License (entitlement)

BALDUR_LICENSE_KEY=<base64>
BALDUR_LICENSE_FILE=/etc/baldur/license

Storage

BALDUR_REDIS_URL=redis://localhost:6379
BALDUR_REDIS_PASSWORD=<secret>            # Redis instance / Sentinel master password
BALDUR_REDIS_SENTINEL_PASSWORD=<secret>   # Sentinel-node password (separate from master)
BALDUR_REDIS_USERNAME=<acl-user>          # Redis 6.0+ ACL username
BALDUR_SQL_DSN=postgresql://user:pass@host:5432/db

BALDUR_SQL_DSN is the canonical full-connection input. The discrete BALDUR_POSTGRES_HOST, BALDUR_POSTGRES_PORT, BALDUR_POSTGRES_DATABASE, and BALDUR_POSTGRES_USER vars are a postgres-only fallback, used only when BALDUR_SQL_DSN is unset; they carry no password, so prefer the DSN for authenticated connections.

BALDUR_REDIS_URL is the canonical Redis routing input for the cache, circuit breaker, DLQ, audit-flush, resilient storage, and tiered-LOCAL. A per-feature override (BALDUR_RESILIENT_STORAGE_REDIS_URL, BALDUR_TIERED_REDIS_LOCAL_URL, AUDIT_HASH_CHAIN_REDIS_URL) wins where set; otherwise the consumer falls back to BALDUR_REDIS_URL.

Redis credentials are configured separately from BALDUR_REDIS_URL and are never embedded in it — keeping passwords out of the URL avoids leaking them into logs, stack traces, and APM. BALDUR_REDIS_PASSWORD authenticates the Redis instance (the master, under Sentinel); BALDUR_REDIS_SENTINEL_PASSWORD authenticates the Sentinel nodes themselves when they require auth separate from the master; BALDUR_REDIS_USERNAME supplies a Redis 6.0+ ACL username. Set only the ones your deployment needs, and use the rediss:// / rediss+sentinel:// scheme for TLS.

The RQ queue adapter is not yet routed through BALDUR_REDIS_URL and still reads only a bare, non-prefixed REDIS_URL. On that path, clear any leftover bare REDIS_URL so it cannot route the queue to a different Redis than your BALDUR_REDIS_URL. The core Redis client's environment fallback prefers BALDUR_REDIS_URL and reads a bare REDIS_URL only as a last-resort fallback when the prefixed variable is unset, so a stray bare REDIS_URL can no longer misroute it.

Behavioral change (v1.x): the audit-flush tasks and distributed hash chain previously read a bare, non-prefixed REDIS_URL env var with a hardcoded redis://localhost:6379 default. They now resolve through BALDUR_REDIS_URL. A deployment that set only the undocumented bare REDIS_URL (and not BALDUR_REDIS_URL) must switch to BALDUR_REDIS_URL. This is not an automated rename (scripts/migrate_baldur_env_vars.py covers only BALDUR_*-prefixed keys).

Health check

Readiness probes every configured database under a bounded budget. A database that refuses connections always fails readiness. This variable decides the other case: a database that accepts the connection but never answers, and so exceeds the probe budget.

not_ready (the default) depools the pod, fast and honestly — the same outcome a hung probe reaches today through the orchestrator's own probe timeout, but decided by Baldur and visible in the response body. Choose ready when every pod shares one database: there, depooling on a database stall takes the whole service out of rotation at once, and staying in rotation degraded is the better failure mode. Either way the affected alias is reported as timed_out in the readiness body, so the stall is never silent.

BALDUR_HEALTH_CHECK_READINESS_TIMEOUT_FAIL_DIRECTION=not_ready

Event logging (runtime level adjustment)

BALDUR_EVENT_LOGGING_DLQ_LOG_LEVEL=INFO
BALDUR_EVENT_LOGGING_CB_LOG_LEVEL=WARNING
BALDUR_EVENT_LOGGING_REPLAY_LOG_LEVEL=INFO
BALDUR_EVENT_LOGGING_SLA_LOG_LEVEL=WARNING

Circuit Breaker Slack push (OSS)

Set a Slack incoming-webhook URL and Baldur posts a message when a circuit breaker opens or recovers. This is the one external notification the OSS tier sends on its own; with the URL unset the open/close events are logged but nothing is posted. The variable sits under the META_WATCHDOG namespace, but on OSS only the circuit-breaker push reads it (the autonomous escalation paging below is PRO). A set URL posts for real from any process that handles these events, including local development, so leave it unset locally to avoid posting to shared channels.

BALDUR_META_WATCHDOG_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...

Meta-Watchdog (self-monitoring, PRO)

Autonomous self-monitoring of Baldur's own healing subsystems. On detection of a stuck/dead subsystem it pages a human through Slack or PagerDuty; it does not self-recover (autonomous recovery is deferred). Default-on under PRO — set BALDUR_META_WATCHDOG_ENABLED=false to silence. Escalation pages deliver to the same BALDUR_META_WATCHDOG_SLACK_WEBHOOK_URL documented in the circuit-breaker push section above.

BALDUR_META_WATCHDOG_ENABLED=true
BALDUR_META_WATCHDOG_ESCALATION_ENABLED=true
BALDUR_META_WATCHDOG_PROBE_INTERVAL_SECONDS=30
BALDUR_META_WATCHDOG_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
BALDUR_META_WATCHDOG_PAGERDUTY_ROUTING_KEY=<pd-key>

Escalation only reaches you while the process is alive to send it. The outbound liveness beacon covers the other case: set BEACON_URL and the watchdog loop GETs it once per completed probe pass, so an external dead-man's-switch service pages on the absence of pings when the process crashes, is OOM-killed or hangs. Unset is the off switch (there is no separate enable flag). FAIL_URL is optional and only routes UNHEALTHY passes elsewhere — silence is never used to signal degradation, and with it unset an UNHEALTHY pass still pings BEACON_URL. TIMEOUT_SECONDS (1–10) is the socket budget of the beacon's own sender thread and bounds nothing on the watchdog loop. Setup, provider choice and grace-period sizing: docs/runbooks/meta-watchdog-escalation-response.md.

BALDUR_META_WATCHDOG_BEACON_URL=https://<dms-provider>/ping/<check-id>
BALDUR_META_WATCHDOG_BEACON_FAIL_URL=https://<dms-provider>/ping/<check-id>/fail
BALDUR_META_WATCHDOG_BEACON_TIMEOUT_SECONDS=5

Metrics source (canary live evaluation)

Connects Baldur to a Prometheus (or PromQL-compatible) metrics backend so the canary live-evaluation gate can compare canary vs. stable traffic over the evaluation window. Leave BALDUR_PROMETHEUS_URL unset and nothing is wired — behavior is unchanged. Set it and baldur.init() registers the provider automatically (an unset URL is the off switch — there is no separate enable flag). HEADERS carries auth/tenancy credentials and is never logged. METRIC_NAMING selects the query templates: baldur targets the built-in baldur_http_* RED metrics, otel targets the OpenTelemetry HTTP-server semantic-convention metrics. In a multi-service cluster set EXTRA_LABEL_SELECTORS so queries are scoped to the target service instead of aggregating the whole Prometheus. The remaining overrides let you point at a third-party exporter's metric/label names.

BALDUR_PROMETHEUS_URL=http://prometheus:9090
BALDUR_PROMETHEUS_HEADERS='{"Authorization": "Bearer <token>", "X-Scope-OrgID": "tenant-a"}'
BALDUR_PROMETHEUS_TLS_VERIFY=true
BALDUR_PROMETHEUS_TLS_CA_CERT=/etc/ssl/certs/prometheus-ca.pem
BALDUR_PROMETHEUS_TIMEOUT_SECONDS=5.0
BALDUR_PROMETHEUS_RETRY_TOTAL=1
BALDUR_PROMETHEUS_RETRY_BACKOFF_FACTOR=0.5
BALDUR_PROMETHEUS_METRIC_NAMING=baldur
BALDUR_PROMETHEUS_EXTRA_LABEL_SELECTORS='{"namespace": "prod"}'
BALDUR_PROMETHEUS_SERVICE_LABEL=
BALDUR_PROMETHEUS_REQUESTS_TOTAL_METRIC=
BALDUR_PROMETHEUS_DURATION_HISTOGRAM_METRIC=
BALDUR_PROMETHEUS_STATUS_CODE_LABEL=
BALDUR_PROMETHEUS_ERROR_STATUS_REGEX=5..