Components and hosts
No host name or path uses a word that ad-block filter lists penalise (analytics, track,
collect, beacon, pixel, stats, metrics, event, data, umami);
tests/adblock checks this
against current lists daily. Umami’s own collect routes are never reachable from the internet.
The edge accepts traffic with no dependency on PostgreSQL or Umami, so ingestion stays up
while the core tier is down (reference/availability.md).
Infrastructure is OpenTofu (
infra/tofu) and Ansible (infra/ansible), see
deploy/overview.md.
Web event flow
- The daily secret lives only in the memory-backed NATS bucket
edge_secretsand is gone at the next UTC midnight, so stored sessions cannot be linked to a person afterwards (compliance/privacy-strategy.md). - Each site has two Umami websites: the primary band and the quarantine band, for traffic
from one network that shows more distinct visitors in an hour than
IPCLUSTER_THRESHOLDallows (operators/reading-the-numbers.md). - The proof raises the cost of forged traffic and rejects naive server-side posts; it does not prove a real browser (security/bot-and-inflation.md, reference/browser-proof.md).
- Wire format: reference/wire-v1.md. Metric definitions: reference/web-metrics.md.
API metrics flow
- Metric rows (
source = metric) and span-derived rows (source = span) are stored apart at every resolution and never added (reference/collector.md, reference/api-storage.md). - Tenant identity always comes from the token;
service.nameis a sub-dimension inside a tenant.
Control plane, tenancy and the operator view
- Tenants sign in with GitHub. Each tenant maps to one Umami team; each console user to one Umami user with view-only rights whose password is derived per UTC day and never stored. The tenant’s web dashboard is Umami itself, reached by a single sign-on redirect.
- Isolation: every tenant-scoped request runs in a transaction with
app.tenant_idset, under a role with row-level security forced on every tenant table. No application role can bypass it. The isolation gatetests/isolationchecks every route and the role matrix in CI (security/threat-model.md). - Operators see aggregates only: the role
cp_operatorreadsagg, hourly and dailyapidata and non-secret project facts, cannot read raw events or sessions, and cannot connect to theumamidatabase. Operators get no Umami access (operators/overview.md). - Machine consumers read the same aggregates with operator API keys under
/api/metrics/v1/, which has no session handling at all (operators/api-keys.md, reference/openapi/metrics-v1.yaml). - Full HTTP contract: reference/control-plane-api.md.
Queues and stores
The topology is declared once in
internal/natsx/topology.go. The database layout and roles
are in db/migrations and plan section 4.7 as summarised above.
High availability and operations
- One NATS cluster and one etcd cluster span the three edge nodes. Patroni uses etcd for its leader key; HAProxy on each core node routes to the current leader.
replayer,metricswriterandjobsrun on both core nodes with one active instance each, chosen by leader locks in NATS.metricswriterandjobswrite the lock revision into every transaction and abort if a newer leader wrote, so a paused former leader cannot write.- Backups: pgBackRest on the database nodes archives WAL and takes scheduled full and differential backups from the Patroni leader to object storage. The local converge uses a separate repository node instead, because object storage does not exist there (deploy/local-converge.md).
- Monitoring: Prometheus, Alertmanager and Grafana on the core nodes, the prober and Gatus on
the probe node (reference/observability.md,
runbooks/alerts.md). The observability role is built and tested on a
local stack and is not yet applied by
site.yml. - Measured and configured failure windows, single points of failure and drill results: reference/availability.md, sla.md.