Architecture & deployment.
Everything runs as a small set of services on a shared docker host. Two databases — one for platform state, one for customer data — and a handful of background workers that do scheduled work (features, inference, alerts).
System diagram
orange = the hot path · dashed = async / scheduled work
The two databases
nixdb · the control plane
Small, opinionated, portable. Holds platform state.
- workspaces, templates, versions
- users, API keys, permissions
- connector + notifier configs
- alert rules, schedules
postgres :5432
nixdatadb · the data plane
TimescaleDB hypertables. Per-workspace isolation.
- datastore tables: {key}_wid{N}
- featurestore tables: same pattern
- inferencestore tables: same pattern
- maintenance / events / lifecycle data
postgres :5433
Deploy patterns
Local dev
docker compose up · nix dev api start · nix dev ui start. Hot reload on both sides.
Customer deploy not battle-tested
Theoretical shape: single VM or VPC, docker compose with persisted volumes, one template → one workspace per customer. Not exercised in production yet — exact shape will depend heavily on the customer's own infra constraints.
Hosted (current)
Customer-specific: Azure VM for XRI's live data DB, local docker for dev/demo, separate stacks per customer.
Architectural principles
Isolation
Workspaces never share tables. Same template, different physical resources. Customer A's data is physically separate from Customer B's.
Additive evolution
Templates can grow, not break. Adding a metric or column = safe. Removing or changing types = new version.
Hot reload everywhere
Python changes are picked up by the backend without restart. Never docker compose restart.
SQL-first
Metrics are mostly SQL — readable, debuggable, no hidden DSLs. Python only when we need transforms or ML.