# Volt Architecture Volt is a unified platform management CLI built on three engines: - **Voltainer** — Container engine (`systemd-nspawn`) - **Voltvisor** — Virtual machine engine (KVM/QEMU) - **Stellarium** — Content-addressed storage (CAS) This document describes how they work internally and how they integrate with the host system. ## Design Philosophy ### systemd-Native Volt works **with** systemd, not against it. Every workload is a systemd unit: - Containers are `systemd-nspawn` machines managed via `volt-container@.service` - VMs are QEMU processes managed via `volt-vm@.service` - Tasks are `systemd timer` + `service` pairs - All logging flows through the systemd journal This gives Volt free cgroup integration, dependency management, process tracking, and socket activation. ### One Binary The `volt` binary at `/usr/local/bin/volt` handles everything. It communicates with the volt daemon (`voltd`) over a Unix socket at `/var/run/volt/volt.sock`. For read-only operations like `volt ps`, `volt top`, and `volt service list`, the CLI can query systemd directly without the daemon. ### Human-Readable Everything Every workload has a human-assigned name. `volt ps` shows names, not hex IDs. Status columns use natural language (`running`, `stopped`, `failed`), not codes. ## Voltainer — Container Engine ### How Containers Work Voltainer containers are `systemd-nspawn` machines. When you create a container: 1. **Image resolution**: Volt locates the rootfs directory under `/var/lib/volt/images/` 2. **Rootfs copy**: The image rootfs is copied (or overlaid) to `/var/lib/volt/containers//rootfs/` 3. **Unit generation**: A systemd unit file is generated at `/var/lib/volt/units/volt-container@.service` 4. **Network setup**: A veth pair is created, one end in the container namespace, the other attached to the specified bridge (default: `volt0`) 5. **Start**: `systemctl start volt-container@.service` launches `systemd-nspawn` with the appropriate flags ### Container Lifecycle ``` create → stopped → start → running → stop → stopped → delete ↑ | └── restart ───────┘ ``` State transitions are all mediated through systemd. `volt container stop` is `systemctl stop`. `volt container start` is `systemctl start`. This means systemd handles process cleanup, cgroup teardown, and signal delivery. ### Container Isolation Each container gets: - **Mount namespace**: Own rootfs, bind mounts for volumes - **PID namespace**: PID 1 is the container init - **Network namespace**: Own network stack, connected via veth to bridge - **UTS namespace**: Own hostname - **IPC namespace**: Isolated IPC - **cgroup v2**: Resource limits (CPU, memory, I/O) enforced via cgroup controllers Containers share the host kernel. They are not VMs — there is no hypervisor overhead. ### Container Storage ``` /var/lib/volt/containers// ├── rootfs/ # Container filesystem ├── config.json # Container configuration (image, resources, network, etc.) └── state.json # Runtime state (PID, IP, start time, etc.) ``` Volumes are bind-mounted into the container rootfs at start time. ### Resource Limits Resource limits map directly to cgroup v2 controllers: | Volt Flag | cgroup v2 Controller | File | |-----------|---------------------|------| | `--memory 1G` | `memory.max` | Memory limit | | `--cpu 200` | `cpu.max` | CPU quota (percentage × 100) | Limits can be updated on a running container via `volt container update`, which writes directly to the cgroup filesystem. ## Voltvisor — VM Engine ### How VMs Work Voltvisor manages KVM/QEMU virtual machines. When you create a VM: 1. **Image resolution**: The base image is located or pulled 2. **Disk creation**: A qcow2 disk is created at `/var/lib/volt/vms//disk.qcow2` 3. **Kernel selection**: The appropriate kernel is selected from `/var/lib/volt/kernels/` based on the `--kernel` profile 4. **Unit generation**: A systemd unit is generated at `/var/lib/volt/units/volt-vm@.service` 5. **Start**: `systemctl start volt-vm@.service` launches QEMU with appropriate flags ### Kernel Profiles Voltvisor supports multiple kernel profiles: | Profile | Description | |---------|-------------| | `server` | Default. Optimized for server workloads. | | `desktop` | Includes graphics drivers, input support for VDI. | | `rt` | Real-time kernel for latency-sensitive workloads. | | `minimal` | Stripped-down kernel for maximum density. | | `dev` | Debug-enabled kernel with extra tracing. | ### VM Storage ``` /var/lib/volt/vms// ├── disk.qcow2 # Primary disk image ├── config.json # VM configuration ├── state.json # Runtime state └── snapshots/ # VM snapshots └── .qcow2 ``` ### VM Networking VMs connect to volt bridges via TAP interfaces. The TAP device is created when the VM starts and attached to the specified bridge. From the network's perspective, a VM on `volt0` and a container on `volt0` are peers — they communicate at L2. ### VM Performance Tuning Voltvisor supports hardware-level tuning: - **CPU pinning**: Pin vCPUs to physical CPUs via `volt tune cpu pin` - **Hugepages**: Use 2M or 1G hugepages via `volt tune memory hugepages` - **I/O scheduling**: Set per-device I/O scheduler via `volt tune io scheduler` - **NUMA awareness**: Pin to specific NUMA nodes ## Stellarium — Content-Addressed Storage ### How CAS Works Stellarium is the storage backend shared by Voltainer and Voltvisor. Files are stored by their content hash (BLAKE3), enabling: - **Deduplication**: Identical files across images are stored once - **Integrity verification**: Every object can be verified against its hash - **Efficient transfer**: Only missing objects need to be pulled ### CAS Layout ``` /var/lib/volt/cas/ ├── objects/ # Content-addressed objects (hash → data) │ ├── ab/ # First two chars of hash for fanout │ │ ├── ab1234... │ │ └── ab5678... │ └── cd/ │ └── cd9012... ├── refs/ # Named references to object trees │ ├── images/ │ └── manifests/ └── tmp/ # Temporary staging area ``` ### CAS Operations ```bash # Check store health volt cas status # Verify all objects volt cas verify # Garbage collect unreferenced objects volt cas gc --dry-run volt cas gc # Build CAS objects from a directory volt cas build /path/to/rootfs # Deduplication analysis volt cas dedup ``` ### Image to CAS Flow When an image is pulled: 1. The rootfs is downloaded/built (e.g., via debootstrap) 2. Each file is hashed and stored as a CAS object 3. A manifest is created mapping paths to hashes 4. The manifest is stored as a ref under `/var/lib/volt/cas/refs/` When a container is created from that image, files are assembled from CAS objects into the container rootfs. ## Filesystem Layout ### Configuration ``` /etc/volt/ ├── config.yaml # Main configuration file ├── compose/ # System-level Constellation definitions └── profiles/ # Custom tuning profiles ``` ### Persistent Data ``` /var/lib/volt/ ├── containers/ # Container rootfs and metadata ├── vms/ # VM disks and state ├── kernels/ # VM kernels ├── images/ # Downloaded/built images ├── volumes/ # Named persistent volumes ├── cas/ # Stellarium CAS object store ├── networks/ # Network configuration ├── units/ # Generated systemd unit files └── backups/ # System backups ``` ### Runtime State ``` /var/run/volt/ ├── volt.sock # Daemon Unix socket ├── volt.pid # Daemon PID file └── locks/ # Lock files for concurrent operations ``` ### Cache (Safe to Delete) ``` /var/cache/volt/ ├── cas/ # CAS object cache ├── images/ # Image layer cache └── dns/ # DNS resolution cache ``` ### Logs ``` /var/log/volt/ ├── daemon.log # Daemon operational log └── audit.log # Audit trail of all state-changing operations ``` ## systemd Integration ### Unit Templates Volt uses systemd template units to manage workloads: | Unit | Description | |------|-------------| | `volt.service` | Main volt daemon | | `volt.socket` | Socket activation for daemon | | `volt-network.service` | Network bridge management | | `volt-dns.service` | Internal DNS resolver | | `volt-container@.service` | Per-container unit | | `volt-vm@.service` | Per-VM unit | | `volt-task-.timer` | Per-task timer | | `volt-task-.service` | Per-task service | ### Journal Integration All workload logs flow through the systemd journal. `volt logs` queries the journal with appropriate filters: - Container logs: `_SYSTEMD_UNIT=volt-container@.service` - VM logs: `_SYSTEMD_UNIT=volt-vm@.service` - Service logs: `_SYSTEMD_UNIT=.service` - Task logs: `_SYSTEMD_UNIT=volt-task-.service` ### cgroup v2 Volt relies on cgroup v2 for resource accounting and limits. The cgroup hierarchy: ``` /sys/fs/cgroup/ └── system.slice/ ├── volt-container@web.service/ # Container cgroup ├── volt-vm@db-primary.service/ # VM cgroup └── nginx.service/ # Service cgroup ``` This is where `volt top` reads CPU, memory, and I/O metrics from. ## ORAS Registry Volt includes a built-in OCI Distribution Spec compliant container registry. The registry is backed entirely by Stellarium CAS — there is no separate storage engine. ### CAS Mapping The key insight: **an OCI blob digest IS a CAS address**. When a client pushes a blob with digest `sha256:abc123...`, that blob is stored directly as a CAS object at `/var/lib/volt/cas/objects/ab/abc123...`. No translation, no indirection. ``` OCI Client Volt Registry Stellarium CAS ───────── ───────────── ────────────── PUT /v2/myapp/blobs/uploads/... ─→ Receive blob ─→ Store as CAS object Content: Compute sha256 digest objects/ab/abc123... ←────────────────────────────────────────────────────────────── 201 Created Index digest→repo Location: sha256:abc123... in refs/registry/ ``` Manifests are stored as CAS objects too, with an additional index mapping `repository:tag → digest` under `/var/lib/volt/cas/refs/registry/`. ### Deduplication Because all storage is CAS-backed, deduplication is automatic and cross-system: - Two repositories sharing the same layer → stored once - A registry blob matching a local container image layer → stored once - A snapshot and a registry artifact sharing files → stored once ### Architecture ``` ┌────────────────────┐ │ OCI Client │ (oras, helm, podman, skopeo, etc.) │ (push / pull) │ └────────┬───────────┘ │ HTTP/HTTPS (OCI Distribution Spec) ┌────────┴───────────┐ │ Registry Server │ volt registry serve --port 5000 │ (Go net/http) │ │ │ │ ┌──────────────┐ │ │ │ Tag Index │ │ refs/registry// → digest │ │ Manifest DB │ │ refs/registry//manifests/ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ Auth Layer │ │ HMAC-SHA256 bearer tokens │ │ │ │ Anonymous pull (configurable) │ └──────────────┘ │ └────────┬───────────┘ │ Direct read/write ┌────────┴───────────┐ │ Stellarium CAS │ objects/ (content-addressed by sha256) │ /var/lib/volt/cas │ └────────────────────┘ ``` See [Registry](registry.md) for usage documentation. --- ## GitOps Pipeline Volt's built-in GitOps system links Git repositories to workloads for automated deployment. ### Pipeline Architecture ``` ┌──────────────┐ ┌──────────────────────────┐ ┌──────────────┐ │ Git Provider │ │ Volt GitOps Server │ │ Workloads │ │ │ │ │ │ │ │ GitHub ─────┼──────┼→ POST /hooks/github │ │ │ │ GitLab ─────┼──────┼→ POST /hooks/gitlab │ │ │ │ Bitbucket ──┼──────┼→ POST /hooks/bitbucket │ │ │ │ │ │ │ │ │ │ SVN ────────┼──────┼→ Polling (configurable) │ │ │ └──────────────┘ │ │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ Pipeline Manager │ │ │ │ │ │ │ │ │ │ │ │ 1. Validate webhook │ │ │ │ │ │ 2. Clone/pull repo │─┼──┐ │ │ │ │ 3. Detect Voltfile │ │ │ │ │ │ │ 4. Deploy workload │─┼──┼──→│ container │ │ │ 5. Log result │ │ │ │ vm │ │ └─────────────────────┘ │ │ │ service │ │ │ │ └──────────────┘ │ ┌─────────────────────┐ │ │ │ │ Deploy History │ │ │ │ │ (JSON log) │ │ │ ┌──────────────┐ │ └─────────────────────┘ │ └──→│ Git Cache │ └──────────────────────────┘ │ /var/lib/ │ │ volt/gitops/ │ └──────────────┘ ``` ### Webhook Flow 1. Git provider sends a push event to the webhook endpoint 2. The GitOps server validates the HMAC signature against the pipeline's configured secret 3. The event is matched to a pipeline by repository URL and branch 4. The repository is cloned (or pulled if cached) to `/var/lib/volt/gitops//` 5. Volt scans the repo root for `volt-manifest.yaml`, `Voltfile`, or `volt-compose.yaml` 6. The workload is created or updated according to the manifest 7. The result is logged to the pipeline's deploy history ### SVN Polling For SVN repositories, a polling goroutine checks for revision changes at the configured interval (default: 60s). When a new revision is detected, the same clone→detect→deploy flow is triggered. See [GitOps](gitops.md) for usage documentation. --- ## Ingress Proxy Volt includes a built-in reverse proxy for routing external HTTP/HTTPS traffic to workloads. ### Architecture ``` ┌─────────────────┐ │ Internet │ │ (HTTP/HTTPS) │ └────────┬────────┘ │ ┌────────┴────────┐ │ Ingress Proxy │ volt ingress serve │ │ Ports: 80 (HTTP), 443 (HTTPS) │ ┌───────────┐ │ │ │ Router │ │ Hostname + path prefix matching │ │ │ │ Route: app.example.com → web:8080 │ │ │ │ Route: api.example.com/v1 → api:3000 │ └─────┬─────┘ │ │ │ │ │ ┌─────┴─────┐ │ │ │ TLS │ │ Auto: ACME (Let's Encrypt) │ │ Terminator│ │ Manual: user-provided certs │ │ │ │ Passthrough: forward TLS to backend │ └───────────┘ │ │ │ │ ┌───────────┐ │ │ │ Health │ │ Backend health checks │ │ Checker │ │ Automatic failover │ └───────────┘ │ └────────┬────────┘ │ Reverse proxy to backends ┌────────┴────────┐ │ Workloads │ │ web:8080 │ │ api:3000 │ │ static:80 │ └─────────────────┘ ``` ### Route Resolution Routes are matched in order of specificity: 1. Exact hostname + longest path prefix 2. Exact hostname (no path) 3. Wildcard hostname + longest path prefix ### TLS Modes | Mode | Description | |------|-------------| | `auto` | Automatic certificate provisioning via ACME (Let's Encrypt). Volt handles certificate issuance, renewal, and storage. | | `manual` | User-provided certificate and key files. | | `passthrough` | TLS is forwarded to the backend without termination. | ### Hot Reload Routes can be updated without proxy restart: ```bash volt ingress reload ``` The reload is zero-downtime — existing connections are drained while new connections use the updated routes. See [Networking — Ingress Proxy](networking.md#ingress-proxy) for usage documentation. --- ## License Tier Feature Matrix | Feature | Free | Pro | |---------|------|-----| | Containers (Voltainer) | ✓ | ✓ | | VMs (Voltvisor) | ✓ | ✓ | | Services & Tasks | ✓ | ✓ | | Networking & Firewall | ✓ | ✓ | | Stellarium CAS | ✓ | ✓ | | Compose / Constellations | ✓ | ✓ | | Snapshots | ✓ | ✓ | | Bundles | ✓ | ✓ | | ORAS Registry (pull) | ✓ | ✓ | | Ingress Proxy | ✓ | ✓ | | GitOps Pipelines | ✓ | ✓ | | ORAS Registry (push) | — | ✓ | | CDN Integration | — | ✓ | | Deploy (rolling/canary) | — | ✓ | | RBAC | — | ✓ | | Cluster Multi-Node | — | ✓ | | Audit Log Signing | — | ✓ | | Priority Support | — | ✓ | --- ## Networking Architecture ### Bridge Topology ``` ┌─────────────────────────────┐ │ Host Network │ │ (eth0, wlan0, etc.) │ └─────────────┬───────────────┘ │ NAT / routing ┌─────────────┴───────────────┐ │ volt0 (bridge) │ │ 10.0.0.1/24 │ ├──────┬──────┬──────┬─────────┤ │ veth │ veth │ tap │ veth │ │ ↓ │ ↓ │ ↓ │ ↓ │ │ web │ api │ db │ cache │ │(con) │(con) │(vm) │(con) │ └──────┴──────┴──────┴─────────┘ ``` - Containers connect via **veth pairs** — one end in the container namespace, one on the bridge - VMs connect via **TAP interfaces** — the TAP device is on the bridge, passed to QEMU - Both are L2 peers on the same bridge, so they communicate directly ### DNS Resolution Volt runs an internal DNS resolver (`volt-dns.service`) that provides name resolution for all workloads. When container `api` needs to reach VM `db`, it resolves `db` to its bridge IP via the internal DNS. ### Firewall Firewall rules are implemented via `nftables`. Volt manages a dedicated nftables table (`volt`) with chains for: - Input filtering (host-bound traffic) - Forward filtering (inter-workload traffic) - NAT (port forwarding, SNAT for outbound) See [networking.md](networking.md) for full details. ## Security Model ### Privilege Levels | Operation | Required | Method | |-----------|----------|--------| | Container lifecycle | root or `volt` group | polkit | | VM lifecycle | root or `volt` + `kvm` groups | polkit | | Service creation | root | sudo | | Network/firewall | root | polkit | | `volt ps`, `volt top`, `volt logs` | any user | read-only | | `volt config show` | any user | read-only | ### Audit Trail All state-changing operations are logged to `/var/log/volt/audit.log` in JSON format: ```json { "timestamp": "2025-07-12T14:23:01.123Z", "user": "karl", "uid": 1000, "action": "container.create", "resource": "web", "result": "success" } ``` ## Exit Codes | Code | Name | Description | |------|------|-------------| | 0 | `OK` | Success | | 1 | `ERR_GENERAL` | Unspecified error | | 2 | `ERR_USAGE` | Invalid arguments | | 3 | `ERR_NOT_FOUND` | Resource not found | | 4 | `ERR_ALREADY_EXISTS` | Resource already exists | | 5 | `ERR_PERMISSION` | Permission denied | | 6 | `ERR_DAEMON` | Daemon unreachable | | 7 | `ERR_TIMEOUT` | Operation timed out | | 8 | `ERR_NETWORK` | Network error | | 9 | `ERR_CONFLICT` | Conflicting state | | 10 | `ERR_DEPENDENCY` | Missing dependency | | 11 | `ERR_RESOURCE` | Insufficient resources | | 12 | `ERR_INVALID_CONFIG` | Invalid configuration | | 13 | `ERR_INTERRUPTED` | Interrupted by signal | ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `VOLT_CONFIG` | Config file path | `/etc/volt/config.yaml` | | `VOLT_COLOR` | Color mode: `auto`, `always`, `never` | `auto` | | `VOLT_OUTPUT` | Default output format | `table` | | `VOLT_DEBUG` | Enable debug output | `false` | | `VOLT_HOST` | Daemon socket path | `/var/run/volt/volt.sock` | | `VOLT_CONTEXT` | Named context (multi-cluster) | `default` | | `VOLT_COMPOSE_FILE` | Default Constellation file path | `volt-compose.yaml` | | `EDITOR` | Editor for `volt service edit`, `volt config edit` | `vi` | ## Signal Handling | Signal | Behavior | |--------|----------| | `SIGTERM` | Graceful shutdown — drain, save state, stop workloads in order | | `SIGINT` | Same as SIGTERM | | `SIGHUP` | Reload configuration | | `SIGUSR1` | Dump goroutine stacks to log | | `SIGUSR2` | Trigger log rotation |