The stack

These ports are for local development. On a shared or public deployment, bind them to 127.0.0.1 or put them behind your reverse proxy with authentication — none of them should be directly internet-reachable with default credentials.

Metrics: 36 series across 7 subsystems

core/observability/metrics.py defines the full catalog — API, workflow, agent, event, celery, database, and cache metrics — on one Prometheus registry, namespaced seo_*.

Worker metrics need multiprocess collection

The API exposes /metrics via a single ASGI mount — one process, one registry. Celery workers are not one process: the default prefork pool forks multiple children per worker container, each with independent memory. core/observability/worker_metrics.py wires prometheus_client’s multiprocess pattern onto Celery’s worker_init/worker_shutdown signals — set PROMETHEUS_MULTIPROC_DIR and one aggregating HTTP endpoint opens in the worker’s parent process (never a forked child, which would collide on the port). Without this env var set, worker-side metrics (most agent, LLM, and Celery metrics) simply aren’t exported — this is opt-in specifically so local make worker-* and the test suite are unaffected.

27 alert rules, cross-checked in CI

docker/observability/alerts/seo-alerts.yml defines every alert; alerts/README.md maintains a coverage-map table — every metric in metrics.py is either alerted, or explicitly marked not-alerted with a stated reason. scripts/verify/check_alert_coverage.py and check_dashboard_metrics.py enforce this stays true on every push (the verify-observability CI job) — see CLI → Verification Scripts.

Redis: noeviction, deliberately

The same Redis instance backs the Celery broker, the event bus, the registry cache, and the rate limiter. allkeys-lru could silently evict an undelivered broker or stream key under memory pressure — real task or event loss with no error anywhere. noeviction instead fails new writes loudly once the memory ceiling is hit:

Runbooks