Chronoverse separates synchronous domain ownership from asynchronous orchestration. The HTTP gateway serves browser and external clients, gRPC services own domain state, and Kafka workers perform delayed or distributed work.
Runtime topology
The production entry point is Nginx. It serves the dashboard, proxies /api/... to the HTTP server, and disables response buffering for live job log routes.
Domain services
Five gRPC services own users, workflows, jobs, notifications, and analytics. They expose ports 50051 through 50055 inside Compose.
Asynchronous plane
Kafka carries workflows, jobs, job_logs, and analytics topics. Scheduling, workflow preparation, container execution, retained log processing, analytics, and outbox publication scale independently within topic and infrastructure constraints.
Persistence plane
- PostgreSQL: transactional state, runtime nodes, leases, idempotency, outbox rows, and analytics.
- ClickHouse: retained high-volume job logs.
- Redis: sessions, cached reads, live log pub/sub, and image-pull locks.
- Meilisearch: retained log search.
- Runtime agent: Docker-capable node registration, heartbeat, and capacity metadata.
- Docker proxy: token-authenticated, method-and-path-allowlisted node-local container lifecycle access.
- LGTM: OpenTelemetry collection and dashboards.
Design invariant
Workers request state transitions from domain services rather than directly inventing workflow or job state. This keeps replay and ownership rules concentrated at the domain boundary.
Container execution has an additional locality invariant: a container_id is never enough on its own. jobs-service assigns a fresh READY runtime node during claim, persists runtime_node_id and a Docker endpoint snapshot on the job, and workers use that endpoint directly for execution, live logs, cleanup, and expired-lease recovery.
docs/architecture.md