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

clients application services workers & jobs storage Browser UI React + TS FieldAssist (mobile) Expo / RN Voice Logger TBD client External Systems SCADA · APIs · S3 nixapi (FastAPI) REST · auth · UI server metricserver SQL / Py compute alertserver evals · routes alerts mlserver train / serve scheduler cron-style · job runner featurestore job rolling aggregates inference job model → store report job PDF · email nixdb (control plane) nixdatadb (data plane) object storage (S3 / Azure)

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.

← back to DLT   ·   on to tools & verticals →