Skip to content

Platform Containers (L2)

Every container in the platform on one diagram. The L1 view collapsed everything into a single "Axion Platform" bubble; this view opens that bubble.

Container Diagram

Container catalog

Container Tech Port(s) Repo Persistence Group
Sense Mobile App Kotlin / Swift (mobile, separate) Local SQLite (offline cache) Frontend
Planner Web App React 19 + Vite + MapLibre 3000 (dev) axion.sense.web Browser localStorage Frontend
Gen Web App Next.js 15 + ai-sdk 3000 (dev) axion.gen.web Browser localStorage (chat history) Frontend
API Gateway / LB Nginx / cloud LB 443 (cluster ingress) Edge
Sense API .NET 10 + ASP.NET Core 8080 (REST), 8081 (gRPC) axion.sense.backend (stateless) Sense Backend
Sense Worker .NET 10 + KafkaFlow + Hangfire 8080 (health), 8888 (Hangfire UI) axion.sense.backend (stateless) Sense Backend
Gen API .NET 10 + ASP.NET Core + DuckDB.NET 5501 (REST), 5502 (gRPC) axion.gen.backend (stateless) Gen Backend
Gen Worker .NET 10 + Hangfire + Tippecanoe 8080 (health), /hangfire (OIDC-gated UI) axion.gen.backend (stateless) Gen Backend
OpenFGA OpenFGA 1.14 8080 (HTTP), 8081 (gRPC) (Helm in axion.infra) Postgres-backed (axion_sense_permissions) Platform Infra
PostgreSQL Postgres 17 + PostGIS + pg_partman 5432 (managed; configured via env) PVC (cloud) or managed Platform Infra
ClickHouse ClickHouse 26.3 (Altinity Op 0.3.11) 8123 (HTTP), 9000 (TCP) axion.infra/services/clickhouse PVC (1×2 + Keeper) Platform Infra
Object Storage S3 / GCS / MinIO 443 (managed) provider-side Platform Infra
Kafka Apache Kafka (KRaft) 9092 (in-cluster), 9094 (external) (managed) PVC Platform Infra
Valhalla Valhalla (OSM map-matching) 8002 (Helm in axion.infra) OSM data volume Platform Infra
SigNoz SigNoz 0.119 + OTel collector 4317 (OTLP gRPC), 4318 (OTLP HTTP), 3301 (UI) axion.infra/services/signoz ClickHouse-backed Platform Infra

Reading the diagram

  • Solid blue boxes are first-party services (Sense + Gen).
  • Cylinders are stateful (databases, caches, object storage).
  • Pipe shape is Kafka.
  • Grey dashed boxes are external systems (Identity Provider, ML, Citylens, 2GIS, Langfuse).
  • Group labels (Frontend, Edge, Sense Backend, Gen Backend, Platform Infra) are organizational only — Kubernetes namespaces map roughly 1:1.

Key observations

  1. Sense API never proxies frame uploads. Mobile uploads frames directly to S3 via presigned URL. The API only issues the URL and records metadata. This decouples upload throughput from API capacity.
  2. Sense API is dual-port. REST (8080) for the web app and admin tools; gRPC (8081) for the mobile app. See ADR-0002.
  3. Sense Worker is the heavy lifter. Every async path (Kafka consumption, Hangfire jobs, ClickHouse batching, PMTiles generation, Citylens sync) lives there. The API stays request-shaped.
  4. Gen splits API and Worker the same way. Gen API is Hangfire client-only (enqueue + poll); Gen Worker runs the Hangfire server, owns Tippecanoe, and serves an OIDC-gated /hangfire dashboard. Both share the same Postgres job store.
  5. Multiple Postgres databases per cluster. axion_sense (domain), axion_sense_tasks (Hangfire + worker-only tables), and axion_sense_permissions (OpenFGA tuples). Separation lets us back them up on different schedules and isolates Hangfire's churn from the domain DB.
  6. Sense and Gen share Postgres, ClickHouse, S3, OpenFGA. They don't share their domain schemas — Gen has its own database in the same Postgres cluster — but they share the physical clusters. See ADR-0006.
  7. Observability is a fabric. Every service exports OTLP to SigNoz. Gen Web's AI agent additionally exports LLM traces to Langfuse — a parallel, special-purpose observability lane.

Where to go next