Skip to content

Architecture

Krasnikov Maksim edited this page Oct 22, 2026 · 2 revisions

Architecture

3.1 Components

Component Role Technology / approach
Control plane (panel) UI, API, data model, Quadlet generation, change planning, backup scheduler, notifications Rust (axum, sqlx); PostgreSQL for state; Solid SPA embedded in the binary; ConnectRPC API
Agent Executes operations in the context of a workspace user: writes units, daemon-reload, start/stop, access to the Podman socket Rust, same binary as the control plane at Alpha; runs as root with a fixed operation whitelist and switches into the user via systemd-run --machine / D-Bus --machine; Podman REST API via /run/user/UID/podman/podman.sock
systemd + Quadlet Container lifecycle, restarts, dependencies, health, auto-update with rollback Quadlet .container / .pod / .network / .volume / .build; templated units such as web@.container
Podman (rootless) Isolation, networks, in-workspace DNS, secrets, image builds netavark + aardvark-dns; crun; podman secret; buildah
Edge proxy Domains, TLS, routing to upstreams, graceful switch on deploy Caddy driven via admin API; optional HAProxy on L4 in front
Registry Stores built images by digest for dev → prod promotion Local OCI registry (zot or distribution)
Logs Centralised logs with rotation and rate limiting journald (LogDriver=journald), read through the journal API
Notifier Events and alerts Telegram; subscribes to podman events and OnFailure= of units
Backup runner Database dumps and upload to S3 on schedule systemd timers + restic/rclone; pg_dump, clickhouse-backup
MCP server Single programmatic entry point for the agent, Telegram bot, Claude Code and external clients Thin layer over the panel API with scoped tokens
LLM adapter Any provider via the user's own key Vercel AI SDK or LiteLLM; tool calling
Task tracker Tasks, cycles, roles; widgets in the UI; agent as task author Plane (self-hosted) shipped with the platform; Jira/Linear drivers
Telemetry Per-workspace / per-service metrics, centralised logs, event feed VictoriaMetrics + VictoriaLogs, Vector/Alloy, cgroups exporter; charts in the Maurice UI; Grafana optional

3.2 Flow

User / Claude Code / Telegram
        |
        v
   [UI + API + MCP] <-- tokens with scopes read / plan / apply
        |
        v
   [Control plane] -- generates Quadlet units, change plans, Caddy config
        |                         |
        v                         v
     [Agent] --machinectl--> [ws-<project>] rootless Podman + systemd --user
        |                         |- web@.service        (stateless, blue-green)
        |                         |- postgres.service    (stateful, restart)
        |                         |- <project>.network   (aardvark DNS: postgres, web, ...)
        v
   [Caddy admin API] <-- routes domain -> 127.0.0.1:<port>
        |
   [HAProxy L4, optional] <-- public IPs, PROXY protocol

3.3 Isolation and users

  • A workspace is a system user ws-<project>: useradd --system, nologin shell, home on NVMe (e.g. /srv/ws/<project>), explicitly allocated subuid/subgid (65536 per workspace, ranges tracked by the panel), loginctl enable-linger.
  • Services inside a workspace trust each other and share the user. A dedicated user ws-<project>-<service> is created only when the isolate flag is set (signers, parsers of untrusted input).
  • Nodes and ClickHouse that need full network performance run rootful with Network=host; for them the panel only offers lifecycle management.
  • Deleting a workspace: stop units → podman system reset as the user → disable-linger → userdel -r → release the subuid range.

3.4 Network and ports

  • Every workspace has its own network; service names resolve through the built-in DNS. The same hostname postgres in different workspaces does not clash.
  • Container names are unique per version (web-a1b2c3); the service name is a stable NetworkAlias (web). Everything talks via the alias.
  • Publishing a port is an explicit action: host port, host IP (default 127.0.0.1 for the proxy), optionally 0.0.0.0.
  • Cross-workspace connectivity goes through an access link: the service is published on an internal host address, the panel shows host.containers.internal:<port> and can revoke the link.
  • Multiple public IPs separate zones (admin/SSH, public services, node P2P), not TLS.

3.5 Zero-downtime deploy (stateless)

  • Start web@<new> without the alias; with Notify=healthy, systemctl start returns only after the health check passes.
  • Switch traffic: Caddy admin API changes the upstream; for internal traffic, network connect --alias on the new container, disconnect on the old. Two containers never hold the same alias at once.
  • The old container gets SIGTERM and drains within StopTimeout. Graceful shutdown in applications is mandatory.
  • The previous image stays in the registry; rollback is the same process in reverse.

Clone this wiki locally