Skip to content

Circuit Breaker

NORA includes a circuit breaker that tracks upstream failures and short-circuits requests when an upstream is known to be down. This avoids slow timeouts cascading into your build pipelines.

Docker, PyPI, npm and Maven keep one breaker per configured upstream, so a dead upstream does not cut off a healthy one (Maven after 1.4.3; before, one breaker covered all of maven.proxies); their keys are <format>:<upstream URL> (for example pypi:https://pypi.org/simple), with any user:pass@ removed. Every other format keeps one breaker per format, keyed by its name.

The circuit breaker is disabled by default and must be explicitly enabled.


VariableDefaultDescription
NORA_CB_ENABLEDfalseEnable upstream circuit breaker
NORA_CB_THRESHOLD5Consecutive failures before opening the circuit
NORA_CB_RESET_TIMEOUT30Seconds to wait before probing a failed upstream

The circuit breaker operates as a state machine with three states per upstream registry:

success
┌─────────────────────┐
│ │
▼ N failures │
Closed ──────────► Open ──(reset_timeout)──► HalfOpen
▲ │ │
│ │ reject (503 + │ probe
│ │ Retry-After) │ succeeds
│ ▼ │
│ caller gets 503 │
└────────────────────────────────────────────┘
probe fails → back to Open

Closed — Normal operation. All requests pass through to the upstream. Each failure increments a counter.

Open — The upstream has failed failure_threshold consecutive times. No request is sent to it. Where the format has another upstream to ask — the next entry of maven.proxies, the next Docker upstream in the fallback chain, the next PyPI index — NORA asks that one instead (Maven and Docker after 1.4.3; before, the request ended with 503). When no upstream serves the request, a copy in local storage that could not be revalidated is served stale (x-nora-stale: true); otherwise Maven and Docker answer 404 if another upstream said the artifact is not there, and 503 Service Unavailable with a Retry-After header if not. PyPI answers 503 whenever an upstream was skipped and none served. A Docker name bound to one upstream through namespaces has no other upstream to ask and gets 503.

HalfOpen — After reset_timeout seconds, a single probe request is allowed through. If it succeeds, the breaker closes. If it fails, the breaker re-opens.


[circuit_breaker]
enabled = true
failure_threshold = 5
reset_timeout = 30

Override the threshold or timeout for one upstream with its breaker key, "<format>:<url>" — the configured URL, without a trailing slash or credentials — or for a whole format with its name ("pypi"). Each value is looked up per upstream first, then per format, then falls back to the global setting:

[circuit_breaker]
enabled = true
failure_threshold = 5
reset_timeout = 30
[circuit_breaker.overrides."docker:https://registry-1.docker.io"]
failure_threshold = 10
reset_timeout = 120
[circuit_breaker.overrides."npm:https://registry.npmjs.org"]
failure_threshold = 3
reset_timeout = 60

This lets you be more tolerant of occasional Docker Hub failures while cutting off flaky npm upstreams faster.


MetricTypeLabelsDescription
nora_circuit_breaker_stateGaugeregistryCurrent state: 0 = Closed, 1 = Open, 2 = HalfOpen
nora_circuit_breaker_rejections_totalCounterregistryTotal requests rejected by an open breaker

registry is the breaker key: the format name, or <format>:<upstream URL> for Docker, PyPI, npm and Maven (Maven after 1.4.3; before, registry="maven"). Match a whole format with a regex, registry=~"pypi(:.*)?", not registry="pypi". /health reports, per format, the worst breaker of its upstreams.

Example alert (Prometheus):

- alert: NoraCircuitBreakerOpen
expr: nora_circuit_breaker_state == 1
for: 5m
labels:
severity: warning
annotations:
summary: "NORA circuit breaker open for {{ $labels.registry }}"

services:
nora:
image: getnora/nora:latest
environment:
NORA_CB_ENABLED: "true"
NORA_CB_THRESHOLD: "5"
NORA_CB_RESET_TIMEOUT: "30"
ports:
- "4000:4000"