OpenKor — KVM virtual machine service.
korvm manages the lifecycle of KVM/QEMU virtual machines via libvirt: create, start, stop, snapshot, live migration. Supports multiple hosts; long-running operations are modeled as async Jobs.
Stack: Go 1.22+ · libvirt-go (CGO) · QEMU/KVM · PostgreSQL (vm schema)
Port: 8082 · Externally accessible: No (only via korapi gateway) · License: Apache 2.0
- Linux host: KVM/QEMU + libvirt installed
libvirt-dev— to build CGO bindings (apt install libvirt-dev)- Go 1.22+
- PostgreSQL 15+
- korauth service running (JWKS endpoint required for JWT verification)
git clone https://github.com/OpenKorProject/korvm
cd korvm
cp .env.example .env # Set DATABASE_URL, JWKS_URL and LIBVIRT_DEFAULT_URI
# Start only the DB
docker compose up -d postgres
# Run the service (on a Linux host with KVM enabled)
go run ./cmd/korvmAPI documentation: http://localhost:8082/scalar
docker compose up -d # korvm runs in privileged mode
docker compose logs -f korvm
docker compose down# 1. Build the binary
CGO_ENABLED=1 go build -o /usr/local/bin/korvm ./cmd/korvm
# 2. Create the environment file
sudo mkdir -p /etc/openkor
sudo cp .env.example /etc/openkor/korvm.env
sudo chmod 600 /etc/openkor/korvm.env
# Edit /etc/openkor/korvm.env and fill in the real values
# 3. Create the image pool directory
sudo mkdir -p /var/lib/openkor/images
# 4. Install and enable the unit
sudo cp deploy/korvm.service /etc/systemd/system/korvm.service
sudo systemctl daemon-reload
sudo systemctl enable --now korvmCommon management commands:
sudo systemctl status korvm
sudo journalctl -u korvm -f # follow logs
sudo systemctl restart korvm
sudo systemctl stop korvmNote: The unit runs as
rootbecause KVM device access and libvirt socket access require it. Restrict exposure via thekorapigateway — never expose port 8082 directly to the network.
| Method | Path | Description |
|---|---|---|
| GET/POST | /v1/vm/ |
List / create VMs (async Job) |
| GET | /v1/vm/{id} |
VM detail |
| POST | /v1/vm/{id}/start |
Start |
| POST | /v1/vm/{id}/stop |
Stop (graceful/force) |
| POST | /v1/vm/{id}/reboot |
Reboot |
| DELETE | /v1/vm/{id} |
Delete |
| GET | /v1/vm/{id}/stats |
Live CPU/RAM/IO |
| GET | /v1/vm/{id}/console |
VNC/SPICE connection info |
| POST | /v1/vm/{id}/migrate |
Live migration (async Job) |
| GET/POST | /v1/vm/{id}/snapshots |
List / create snapshots (async Job) |
| POST | /v1/vm/{id}/snapshots/{sid}/revert |
Revert to snapshot (async Job) |
| DELETE | /v1/vm/{id}/snapshots/{sid} |
Delete snapshot |
| GET | /v1/vm/jobs/{jid} |
Job status (poll) |
| GET | /v1/vm/hosts |
Hypervisor host list |
All endpoints require Authorization: Bearer <jwt> (RS256, korauth).
Full spec: api/openapi.yaml · UI: GET /scalar
api/ # OpenAPI spec (openapi.yaml)
cmd/korvm/ # Entrypoint
deploy/ # Deployment artifacts (systemd unit)
internal/
auth/ # JWT verification (JWKS cache)
config/ # Environment variables
domain/ # VM, Job, Snapshot, Host entity types
handler/ # HTTP handlers + middleware
libvirt/ # HypervisorClient interface, CGO impl, Mock, Pool
store/ # Store interfaces
store/postgres/ # pgx implementation
worker/ # Async job runner (VMCreate, VMMigrate, Snapshot*)
xmlbuilder/ # libvirt domain and snapshot XML generation
migrations/ # PostgreSQL schema (vm)
- libvirt abstraction: Behind the
internal/libvirt.HypervisorClientinterface. Handlers do not import libvirt directly. - libvirt is truth, DB is index. On inconsistency, reconcile from libvirt.
- Async Job:
pending → running → completed | failed. VM create, migrate, snapshot create/revert are async; start/stop/reboot are synchronous. - Multi-host: Connection pooled per host. No local-only hypervisor assumption anywhere.
go test ./...
golangci-lint runConventional Commits: feat:, fix:, refactor:, test:, docs:.
License: Apache 2.0.
