A command-line VM manager built on QEMU/KVM. Create, start, SSH into, and monitor virtual machines from a single lightweight tool — with GPU passthrough, virtiofs sharing, SPICE display, and SSH tunnelling wired in.
vee create myvm # create an Ubuntu 24.04 server VM
vee start myvm # boot it (detached by default)
vee ssh myvm # open a shell
vee stop myvm # graceful shutdownmake install # build and install to ~/.vee/bin/vee
vee create myvm # create an Ubuntu 24.04 server VM
vee start myvm # boot — detached by default
vee ssh myvm # open a shell
vee stop myvm # graceful shutdownPrerequisites: KVM access, bridge networking, disk group membership, and OVMF firmware. See docs/prerequisites.md.
macOS (Apple Silicon): vee also runs on Apple Silicon Macs via Hypervisor.framework (HVF) with aarch64 guests and accelerated virtio-gpu. See docs/macos.md for setup, the per-guest GPU matrix, and limitations.
Windows: vee also runs on Windows (amd64) via the Windows Hypervisor Platform (WHPX) with x86-64 guests. VFIO, virtiofs, vsock, bridge networking, and swtpm are Linux-only and degrade gracefully. See docs/windows.md for prerequisites and limitations.
Templates apply sane defaults (memory, CPUs, disks, networking, cloud-init) automatically.
| Template | Description |
|---|---|
ubuntu-server |
Ubuntu 24.04 LTS · UEFI · user-mode NIC (default) |
devbox |
Docker + zsh via cloud-init · --distro flag (ubuntu/arch/fedora) |
server |
openssh + ufw + fail2ban via cloud-init · --distro flag |
desktop |
GNOME + Mesa · accelerated virtio-gpu (virgl) · --distro flag (fedora/ubuntu) · Apple Silicon |
gaming-arch |
Arch Linux + KDE Plasma + Steam · 16G / 8 CPUs · virgl or GPU passthrough |
gaming-bazzite |
Bazzite (Fedora Atomic) gaming ISO · 16G / 8 CPUs · KDE Plasma |
gaming |
Legacy alias for gaming-arch with passthrough |
passthrough |
Raw NVMe boot + GPU passthrough · 8G / 6 CPUs · SPICE · virtiofs |
truenas |
TrueNAS SCALE · AHCI OS disk · bridge NIC · SPICE |
torrent |
Lightweight 2G / 1 CPU · qbittorrent-nox via cloud-init · optional VPN kill-switch |
jellyfin |
Jellyfin · NFS/SMB/host-dir/block/USB media · mDNS |
windows |
Windows · UEFI secure boot · TPM 2.0 |
docker |
Alpine Linux · Docker daemon on tcp://localhost:2375 |
github-runner |
Self-hosted Actions runner · outbound HTTPS long-polling |
vee create mynas --template truenas \
--data-disk /dev/disk/by-id/ata-ST22000NM000C_ZXA0S3H6:EXOS22TB-A \
--data-disk /dev/disk/by-id/ata-ST22000NM000C_ZXA0WD9J:EXOS22TB-Bvee create downloads the base image it needs automatically. You can also pre-fetch
images into the local cache (~/.vee/iso/) with vee pull, so later VMs reuse the
cached copy instead of re-downloading. A pull for an already-cached image is a no-op.
vee pull ubuntu # newest known Ubuntu cloud image
vee pull ubuntu 22.04 # a specific version
vee pull ubuntu-24.04 # same, as a single token
vee pull windows win10 # build the Windows 10 ISO (see below)
vee pull --list # list every supported distro and versionBoth the distro and distro-version forms shell-complete from the built-in list.
| Distro | Notes |
|---|---|
ubuntu |
Cloud image (cloud-init ready) — 24.04, 22.04, 20.04 |
arch |
Bootstrap image |
fedora |
Cloud image (cloud-init ready) — 42, 41 · aarch64 + x86_64 |
alpine |
Cloud image |
bazzite |
Fedora Atomic gaming ISO |
truenas |
TrueNAS SCALE installer ISO |
windows |
Built on demand — win11, win10, server2025, server2022 |
vee builds Windows install ISOs on demand — no manual ISO download required. It
resolves the latest build via the UUP dump API, downloads
the ESD packages directly from Microsoft's servers, and assembles a bootable UEFI
ISO inside a throwaway container (wimlib + xorriso).
vee pull windows win11 # Windows 11 24H2
vee pull windows win10 # Windows 10 22H2
vee pull windows server2025 # Windows Server 2025
vee create winvm --template windows # pulls automatically if not cachedWindows-guest status: both
win10(22H2) andwin11(24H2) install end-to-end, booting to the desktop from the virtio system disk. 24H2 required several workarounds (running Setup from a writable scratch disk, injecting drivers viaofflineServicing, and bundlingwinre.wimintoinstall.wim). See docs/windows-guests.md for the ISO pipeline and docs/windows-24h2-install.md for the full 24H2 debugging writeup.
Requirements: nerdctl or docker on PATH (the ISO is assembled in a
container; no host tooling is installed) and ~15 GB of free scratch space, which
vee allocates next to the ISO cache (under ~/.vee/iso/) so the build works even
when /tmp is a small RAM-backed tmpfs. The windows template additionally
pulls the VirtIO driver ISO and WinFSP so the guest gets paravirtualized disk,
network, and virtiofs support out of the box.
Licensing: vee downloads Windows bits from Microsoft's own servers and assembles the ISO locally — it never redistributes Windows. You still need a valid Windows license key to activate the guest.
The gaming, gaming-arch, and passthrough templates use VFIO to wire a PCIe GPU directly into the VM — zero emulation, full metal.
1 · IOMMU — enable in kernel parameters:
intel_iommu=on iommu=pt # Intel
amd_iommu=on iommu=pt # AMD
2 · vfio-pci kernel modules — /etc/modules-load.d/vfio.conf:
vfio
vfio_iommu_type1
vfio_pci
3 · vfio group membership:
sudo usermod -aG vfio $USER4 · Unlimited locked memory — VFIO DMA-maps all guest RAM:
sudo tee /etc/security/limits.d/vee-vfio.conf <<'EOF'
* - memlock unlimited
EOFRe-login, then verify with ulimit -l → unlimited.
vee gpu list # list PCI addresses and IOMMU groups
sudo vee gpu bind 08:00.0 # bind to vfio-pci (requires root)
vee gpu status 08:00.0 --memory 16G # pre-flight check before bootAll devices in the same IOMMU group must be bound together. vee gpu status reports peer devices and their current drivers.
# Passthrough VM booting from an existing NVMe (Windows or Linux)
vee create linux-gaming --template passthrough \
--nvme-dev /dev/disk/by-id/nvme-... \
--ovmf-vars /path/to/OVMF_VARS.fd \
--gpu-pci 08:00.0
# Fresh Arch gaming VM with passthrough
vee create arch-gaming --template gaming-arch \
--gpu-mode passthrough --gpu-pci 08:00.0vee gpu status 08:00.0 --memory 16G # pre-flight check
vee logs linux-gaming # QEMU log — scan for vfio errors
tail -f ~/.vee/logs/vee.log # structured debug log (VFIO decisions)| Error | Cause | Fix |
|---|---|---|
Permission denied /dev/vfio/N |
User not in vfio group | sudo usermod -aG vfio $USER + re-login |
vfio_container_dma_map = -12 (ENOMEM) |
memlock limit too low | Set memlock unlimited in limits.d/ |
| QEMU process exits immediately | Driver not bound / IOMMU isolation | vee gpu status to diagnose |
| GPU not used in guest | Wrong pci_addr in vm.yaml |
Check gpu.pci_addr in vm.yaml |
See docs/gpu-passthrough-gaming.md for Sunshine + Moonlight streaming.
| Command | Description |
|---|---|
vee create <name> |
Provision a new VM |
vee pull <distro> [version] |
Download or build a base image into the cache |
vee start <name> |
Boot a VM (detached by default) |
vee stop <name> |
Graceful shutdown |
vee list |
List all VMs and status |
vee status <name> |
Show detailed status of a VM |
vee ssh <name> |
Open a shell |
vee ssh-share <name> |
Share host SSH agent into the VM via AF_VSOCK |
vee tunnel <name> [service] |
List VM services, or open/connect to one |
vee ports <name> |
List bound TCP ports and process names in a running VM |
vee ip <name> |
Show network interfaces and IP addresses inside a VM |
vee logs <name> |
Stream QEMU output |
vee monitor <name> |
Real-time CPU / memory / disk / network stats |
vee qmp <name> <command> |
Send a QMP (QEMU Machine Protocol) command to a running VM |
vee view <name> |
Open or connect to a VM's display (SPICE or GPU) |
vee config <name> |
Edit a VM's configuration in an interactive TUI |
vee check <name> |
Run health checks on an installed VM |
vee backup <name> |
Back up directories from a running VM |
vee autostart <name> |
Enable or disable autostart for a VM |
vee delete <name> |
Wipe VM and all its disks |
vee daemon |
Run the vee daemon (starts and watches autostart VMs) |
vee dashboard |
Start a web dashboard for all VMs |
vee gpu list |
List PCI GPUs and IOMMU groups |
vee gpu bind <pci> |
Bind device to vfio-pci (requires root) |
vee gpu unbind <pci> |
Release device back to host driver (requires root) |
vee gpu status <pci> |
Pre-flight check for passthrough |
vee mirror start |
Start host-side pacman caching proxy (pacoloco) |
vee mirror status |
Show pacoloco unit state and cache size |
vee mirror stop |
Stop the pacoloco user unit |
vee mirror purge |
Delete all cached packages on disk |
vee runner key <name> |
Print a runner's GitHub SSH public key |
vee runner snapshot <name> |
Persist a runner's credentials to the host (encrypted) |
vee version |
Print version, commit, and build date |
source <(vee completion bash) # bash
source <(vee completion zsh) # zsh
vee completion fish | source # fishmake hooks # enable the pre-commit hook (fmt check + lint + build) for this clone
make fmt # apply gofumpt + goimports formatting in place
make lint # format check + golangci-lint (mirrors CI)
make build # build the vee binary
make test # go test -race ./...Formatting (gofumpt + goimports) and linting are enforced by a strict
.golangci.yml. make lint runs golangci-lint fmt --diff (fails on any
unformatted code) followed by golangci-lint run; run make fmt to fix
formatting before committing.
The pre-commit hook lives in .githooks/ (tracked) and only runs when Go files
are staged: it runs the format check, lint, and build. Enable it once per clone
with make hooks; bypass a single commit with git commit --no-verify. CI
(lint + format + build + test) reads the Go version from go.mod.
Pushing a v* tag triggers the release workflow
(.github/workflows/release.yml):
git tag v0.4.0
git push origin v0.4.0It cross-compiles the vee binary for every supported host — linux/amd64,
linux/arm64, darwin/amd64, darwin/arm64, windows/amd64 — with the tag,
commit, and build date injected via -ldflags (so vee version reports the
release identity). Each build is packaged as a .tar.gz (binary + LICENSE +
README.md + THIRD_PARTY_LICENSES) alongside a .sha256 checksum, and a
GitHub Release is published whose body lists the commits since the previous tag.
Tags containing a hyphen (e.g. v0.4.0-rc1) are marked as pre-releases.
On Windows, VFIO passthrough, virtiofs, vsock, swtpm, bridge networking, and the systemd daemon are Linux-only and degrade gracefully; the binary runs x86-64 guests under WHPX. See docs/windows.md.
Download the .tar.gz for your platform from the
Releases page, verify it against the
matching .sha256, extract the vee binary onto your PATH, and install the
host QEMU packages. Full step-by-step instructions (Linux, macOS, Windows) plus
how to run vee as a daemon are in the
Installation guide.
The full documentation site is published at https://vee.benehiko.com/. Deep-dive docs also live in this repo:
- docs/prerequisites.md — system setup, groups, bridge networking, OVMF
- docs/macos.md — macOS (Apple Silicon / HVF) host support and the per-guest GPU matrix
- docs/windows.md — Windows (WHPX) host support, feature matrix, and the nested-virtualization limitation
- docs/windows-guests.md — the on-demand Windows guest ISO build pipeline
- docs/windows-24h2-install.md — full writeup of the Windows 11 24H2 install debugging
- docs/qmp.md —
vee qmpand the daemon-routed QMP transport - docs/gpu-passthrough-gaming.md — Sunshine + Moonlight streaming over GPU passthrough
- docs/media-sources.md — attaching NFS/SMB/host-dir/block/USB media to VMs
- docs/pacman-mirror.md — host-side pacman caching proxy for Arch VMs
- docs/host-shutdown.md — how the daemon blocks host poweroff while VMs are running
- docs/install-iso-lifecycle.md — one-shot installer ISO lifecycle and repair
- docs/github-runner.md — self-hosted GitHub Actions runner: cred persistence, SSH keys, disk GC
- docs/docs-site.md — Hugo documentation site: local preview and Cloudflare deploy
MIT — see LICENSE.