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 (planned)

Peer-to-peer live migration

Designed so running VMs move host-to-host directly between agents over mTLS, with the control plane orchestrating the move but never sitting in the data path. Planned, not yet shipped.

snapshots

Snapshots as a primitive

VMs boot directly from an image URL; point-in-time snapshots are managed API objects, not a pile of shell scripts bolted on the side.

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.

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

Point the CLI at a cluster, enrol a node over mTLS, and boot a VM straight from an image URL — no template entity, no registry.

bash
# 1 · point the CLI at your cluster
$ otherix config add cluster --name local \
--server http://localhost:8080 \
--login admin@otherix.local
# 2 · enrol a node — join-token bootstrap (mTLS)
$ otherix node join-token create \
--node-name node-1 --ttl 10m
# 3 · boot a VM straight from an image URL
$ otherix vm create demo-vm \
--image-url https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-arm64.img \
--arch arm64 --vcpus 2 --memory-mb 2048 --wait
output running
$ otherix vm list
NAME STATUS POOL IMAGE
demo-vm running default ubuntu-24.04…img
$ otherix vm console demo-vm
connected to demo-vm (serial console).
press Ctrl+] to detach.
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 in the docs.
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, control plane out of the data path (planned)Yes (cluster)YesYes (KubeVirt)
VM sourceImage URL, no template entityTemplates / clonesGlance imagesImages (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.