open-source · self-hosted · Apache 2.0

Self-hosted VM orchestration, the Kubernetes way

A control plane for KVM/QEMU clusters with a declarative API, reconciliation loops and embedded etcd — engineered for long-lived, stateful VMs, not disposable containers.

Get started View on GitHub
$ curl -fsSL get.otherix.dev | OTHERIX_COMPONENT=cli sh
otherix — dev cluster
$ make local-dev-start
>> Step 1/8 · pre-flight ports topology clear
>> Step 2/8 · build api + agent + cli
>> Step 3/8 · stage Lima VMs otherix-dev-1 · otherix-dev-2
>> Step 5/8 · start otherix-api PID 94084
>> Step 6/8 · wait for CP /healthz http://localhost:8080
>> Step 7/8 · bootstrap agents (mTLS) node-1 node-2
>> Step 8/8 · final sanity
>> local-dev-start complete
etcd data : ~/otherix/.local/etcd (embedded member)
api-server : http://localhost:8080 (PID 94084)
Lima VMs : otherix-dev-1 (node-1) · otherix-dev-2 (node-2)
CLI : ~/otherix/bin/otherix (cluster: dev)
$ ./bin/otherix node list
NAME ARCH STATUS CORDONED AGE
node-1 arm64 ready no 5m
node-2 arm64 ready no 5m
Built on QEMU · embedded etcd · written in Go · Apache 2.0
Why Otherix

VMs aren’t cattle.
They remember.

Container orchestrators assume workloads are disposable replicas. Virtual machines aren’t — they’re long-lived, stateful entities. Otherix borrows the Kubernetes control-plane pattern but drops the assumption that you can throw the disk away.

disks & identity

Stable disks & identities

Disks persist across reboots, reschedules and migrations. Addresses and identities stay fixed. A VM is an addressable object, never a fungible replica.

declarative

Declarative, reconciled

A REST API with generation / observed-generation bookkeeping. Reconciliation loops converge the cluster to your declared spec — the Kubernetes pattern, minus the container assumptions.

migration

Live migration, first-class

Move a running VM with a single change. Memory and disk stream peer-to-peer between agents; the control plane stays out of the data path.

Features

A single binary, no external moving parts

embedded-etcd

Embedded etcd, no database

Embedded etcd is the only datastore the api-server runs — no Postgres, no Redis, no external queue to deploy or back up.

single-binary

Single-binary agents

Each agent installs on a KVM/QEMU host as one static Go binary alongside qemu-system-*. It drives QEMU directly — no libvirt in the path.

live-migration

Peer-to-peer live migration

Running VMs move host to host directly between agents over mTLS; the control plane orchestrates the move but never sits in the data path. Your open console and logs -f sessions follow the VM across the cutover, and so does its network identity, so a move costs milliseconds of pause rather than a reboot.

snapshots

Replicated, self-healing snapshots

Point-in-time snapshots are standalone, content-addressed API objects — not shell scripts bolted on the side. Recreate a VM from one on any node (the disk is pulled peer-to-peer), set a replication factor to keep N copies across the cluster, and let it re-replicate after a node loss. Orphaned blobs are reclaimed automatically and bit-rot is scrubbed and re-replicated — durability is measured from what's actually on disk, never assumed.

images

Boot from a URL, cached cluster-wide

VMs boot straight from an image URL — no template object, no registry to run. Each node caches images content-addressed by digest, and a cached image is pulled peer-to-peer between nodes instead of re-downloaded, so the slow origin fetch happens about once per cluster. Pin exact bytes with a digest, or follow a rolling tag with a Kubernetes-style imagePullPolicy.

mTLS

mTLS end to end

Every control-plane ↔ agent call is mutually authenticated. Agents join over mTLS and report observed state through signed heartbeats.

self-clustering

HA by self-clustering etcd

Run multiple api-server replicas; they form a single self-clustering etcd cluster and coordinate through it. No external quorum service to run.

Networking

A secure L2 fabric that follows your VMs

Overlay networks give VMs on different hypervisors a single layer-2 segment — a VXLAN tunnel riding an encrypted WireGuard underlay mesh. Because the segment spans the whole cluster, a VM keeps its MAC and IP when it live-migrates, and open connections survive the move.

Cluster-wide L2 overlay

VMs across nodes share one broadcast domain over VXLAN, isolated per VNI — no reliance on the physical fabric being flat.

WireGuard-encrypted underlay

Every inter-node frame rides an authenticated WireGuard mesh. The control plane recomputes the full peer set on each heartbeat — no agent-to-agent negotiation.

Deterministic forwarding

The control plane computes the forwarding database and hands it to agents on the heartbeat. VXLAN address learning is off, so the data plane never guesses. When a VM live-migrates, the control plane fast-pushes the updated map and the MAC re-points to the new node, with no flood-and-learn cycle.

encapsulation MTU
physical underlay · eth0 1500
WireGuard mesh · otwg0 · encrypted 1440
VXLAN · otvx1000 · VNI 1000
VM frame · 10.50.0.0/24 1390
db-0
10.50.0.7 · node-1
app-1
10.50.0.9 · node-2
one L2 segment across nodes — IP preserved through live migration
How it works

One control plane, agents on every node

The control plane holds desired state in embedded etcd and reconciles it. Agents drive QEMU on bare metal and report back over mTLS.

otherix CLI
web UI / REST clients
REST
otherix-api · control plane
×3 replicas · self-clustering etcd
scheduler reconcile loops worker queue embedded etcd
mTLS · heartbeat (observed state)
node-1 bare-metal
otherix-agent → qemu-system (no libvirt)
vm/db-0 vm/app-1
live-migration
peer-to-peer
node-2 bare-metal
otherix-agent → qemu-system (no libvirt)
vm/cache-0 vm/app-1 ←
Quick start early development

From zero to a running VM

One command on a fresh Debian/Ubuntu server stands up a complete, fully functional single-node cluster — control plane, hypervisor agent, and a NAT network — then boots a demo VM you can SSH into. No config, no cluster wiring.

bash
# 1 · on a fresh server: install + launch
$ curl -fsSL https://get.otherix.dev/quickstart.sh \
| sudo sh
# 2 · log in to the VM it just created
$ ssh otherix@10.88.0.10
output running
… control plane + agent + NAT network + demo VM
Otherix is up — a full single node — and 'demo' is running.
ssh otherix@10.88.0.10
password for otherix: ••••••••
serial console: otherix vm console demo
Working from the repo? make local-dev-start brings up the whole stack — api-server with embedded etcd, an agent, and a configured CLI — in one command. Full walkthrough, upgrades, and teardown in the single-node quickstart docs.
Done trying it? curl -fsSL get.otherix.dev/uninstall.sh | sudo FORCE=true sh removes everything and wipes all state.
Declarative manifests

Declare the cluster, apply it like kubectl

Describe networks, storage pools and VMs as otherix/v1 documents, then create or delete the whole set with one command — the same control-plane endpoints as the imperative flags, driven from a file.

cluster.yaml
# one file · three kinds · applied in order
apiVersion: otherix/v1
kind: Network
metadata:
name: ex-overlay
spec:
type: overlay # VXLAN over WireGuard
egress: nat
subnet: 10.82.0.0/24
dhcp: true
---
apiVersion: otherix/v1
kind: StoragePool
metadata:
name: ex-pool
spec:
type: local_dir
path: /var/lib/otherix/pools/ex-pool
nodeList: [node-1, node-2]
---
apiVersion: otherix/v1
kind: VM
metadata:
name: web-0
spec:
imageURL: https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-amd64.img
arch: amd64
network: ex-overlay
pool: ex-pool
vcpus: 2
memoryMB: 2048
$ otherix create -f cluster.yaml --wait
$ otherix delete -f cluster.yaml --force
one api, three kinds

Network, StoragePool and VM in a single otherix/v1 file, split by ---. Fields are camelCase; unknown keys are rejected, not ignored.

dependency-ordered

create -f always applies Network → StoragePool → VM so name references resolve; delete -f reverses it.

plan first

--dry-run prints the resolved plan; --wait blocks on VM tasks and pool reconciliation.

Comparison

How Otherix compares

CapabilityotherixProxmox VEOpenStackHarvester
ModelDeclarative desired/observed, Kubernetes-style reconciliation for VMsImperative cluster managerModular IaaS (many services)HCI built on Kubernetes (KubeVirt)
Control-plane datastoreEmbedded etcd, the only stateful serviceCorosync + pmxcfsSQL database + message queueKubernetes etcd + Longhorn
Hypervisor accessQEMU directly, no libvirtQEMU/KVMNova via libvirtKubeVirt (libvirt)
Live migrationPeer-to-peer between agents; the control plane orchestrates but is never in the data path — the guest’s network identity and live logs -f follow the moveYes (cluster)YesYes (KubeVirt)
Snapshots & durabilityStandalone content-addressed snapshots; cross-node replication, automatic reclamation and self-healing brokered by the control plane — no external storage system requiredVM snapshots; ZFS / storage-level replication, PBS backupsCinder volume snapshots & backupsLonghorn volume snapshots, backups & replicas
VM sourceImage URL, no template entityTemplates / clonesGlance imagesImages (URL / Longhorn)
Image distributionContent-addressed node cache; images pulled peer-to-peer between nodes, Kubernetes-style imagePullPolicy — no registry or import serviceTemplates / clones on shared or local storageGlance image serviceImages via URL / Longhorn
FootprintSingle Go binary embeds etcd; no external DBPer-node Debian + cluster stackHeavy, multi-componentFull Kubernetes cluster
Operate & debugSingle binary, one log stream, no external services to correlate; plain Go and direct QEMU — few moving parts to debug or repairPer-node daemons plus Corosync quorum to keep healthyMany services plus DB and message queue to correlate; deep operational expertiseFull Kubernetes plus KubeVirt and Longhorn to operate and debug

Reflects each project’s typical default architecture as of 2026. Capabilities, not endorsements.