A terminal UI and CLI for running local QEMU VMs on Linux. No libvirt, no daemon, one binary.
- Boots an Alpine live VM that comes up already networked and
ssh-reachable: an apkovl overlay is baked into the boot, so there's nosetup-alpinestep to get in. - Also runs Ubuntu, Debian, Fedora, and Arch cloud images, provisioned via cloud-init on first boot.
- Persistent disk VMs for anything else: install once with the guest's own installer, then boot straight to it.
- Ships a few ready-made recipes (XFCE, Docker, dev tools, Tailscale) to run post-boot over ssh or bake into a cloud-init seed.
stoat recipe newscaffolds your own. - QEMU processes are tracked by pidfile, not supervised:
stoatcan exit and the VM keeps running. - A TUI for interactive use, and a scriptable CLI (
ls,up,down,ssh,provision,rm,recipe,logs,doctor) covering the same operations for scripts and automation.
The list screen, with one running VM, one stopped, and one whose vm.toml failed to parse (captured with tmux capture-pane against a throwaway STOAT_HOME):
███████╗████████╗ ██████╗ █████╗ ████████╗
██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗╚══██╔══╝
███████╗ ██║ ██║ ██║███████║ ██║
╚════██║ ██║ ██║ ██║██╔══██║ ██║
███████║ ██║ ╚██████╔╝██║ ██║ ██║
╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
╭────────────────────────────────────────────────────────────────╮
│ │
│ ❯ ○ alpine-live live 2048M 2c - │
│ │
│ ○ ubuntu-dev cloud 4096M 4c - │
│ │
│ ✗ old-vm broken: toml: line 1: expected '.' or │
│ '=', but got 'i' instead │
│ │
╰────────────────────────────────────────────────────────────────╯
↵ start/stop • →/l details • s ssh • p provision • / search • n new • r recipes • d delete • q quit • ? help
The same VMs from stoat ls:
$ stoat ls
NAME MODE STATE CPUS RAM SSH
alpine-live live stopped 2 2048 2200
ubuntu-dev cloud stopped 4 4096 2201
old-vm - broken - - - toml: line 1: expected '.' or, but got 'i' instead
- Linux with KVM:
/dev/kvmreadable and writable by your user (member of thekvmgroup). qemu-system-x86_64andqemu-img. GTK+OpenGL support is needed only for the one VM that opens a window (-display gtk,gl=on, a disk VM mid-install); with no graphical session on the host, that console falls back to VNC and the VM still installs.ssh.xorriso(packagelibisoburn), only if you use a cloud-init image (Ubuntu/Debian/Fedora/Arch). Not needed for Alpine live or disk VMs.- Go 1.26 to build from source (the version pinned in
go.mod).
just setupBuilds stoat, installs it to ~/.local/bin (override with $PREFIX), offers to add that directory to your PATH, and reports which of qemu-system-x86_64, qemu-img, ssh, xorriso, and /dev/kvm still need attention, with the exact install command for your distro.
For a non-interactive install (CI, scripts):
just build && just install # or: make build && make installinstall puts the binary at $PREFIX/stoat (default ~/.local/bin/stoat); make sure that's on your PATH.
Release tarballs and the Nix flake are covered in docs/getting-started/installation.md.
stoat
- Press
nfor a new VM. - Pick an image and press SPACE to download it (not enter: enter tries to create the VM and tells you to download first).
- Press ENTER to create the VM.
- Back on the list, press ENTER to start it.
- If you checked any recipes, stoat waits for sshd and then offers to run
them:
test1 is up, run xfce now? y/N. Pressy, or decline and presspwhenever you like. - Press
sto ssh in.
See docs/getting-started/first-vm.md for the walkthrough with screenshots and troubleshooting.
| Command | Purpose |
|---|---|
stoat |
launch the interactive TUI |
stoat ls |
list VMs, one line per VM |
stoat up <name> |
start a VM |
stoat down <name> |
stop a VM (graceful) |
stoat ssh <name> |
ssh into a VM, replacing this process |
stoat provision <name> |
run recipes, streaming output to stdout |
stoat rm <name> [-y] |
delete a VM; refuses while running, confirms unless -y |
stoat recipe list |
list installed recipes and where they live |
stoat recipe new <name> [--os alpine] [--backend cloudinit] |
scaffold a recipe in the recipes directory |
stoat logs [-n N] |
tail the stoat log (default 50 lines) |
stoat doctor |
check host prerequisites |
stoat version |
print the stoat version |
Global flags: -q/--quiet/--no-interactive suppress progress chatter (results and errors still print). Exit codes: 0 success, 1 runtime failure, 2 usage error. Full reference: docs/reference/cli.md; TUI keys: docs/reference/tui.md.
Every VM is one of three modes:
- live: diskless, boots straight from an Alpine ISO, discards all state on stop. Provisioned by an apkovl overlay built fresh at every start.
- disk: a qcow2 that survives restarts. You run the guest's own installer once, then flip it to "installed". Provisioned by pushing recipes over ssh.
- cloud: a CoW overlay over a shared Ubuntu/Debian/Fedora/Arch base image. Provisioned by a cloud-init seed baked in once, at first boot only.
Each VM is a directory under ~/.stoat (override with $STOAT_HOME) holding a hand-editable vm.toml plus whatever state its mode keeps, nothing else is shared between VMs. See docs/concepts/modes-and-backends.md and docs/concepts/data-root.md.
Recipes (the scripts and cloud-init fragments that install XFCE, Docker, etc.) are covered in docs/recipes/overview.md, including how to write your own.
Pre-1.0 and single-user: stoat assumes it's the only thing managing its ~/.stoat, and offers no sandboxing beyond what QEMU/KVM already give a guest. The Alpine live path is the most exercised mode; the cloud-init backends and disk-mode installs are newer. vm.toml's shape and the CLI's flags may still change before 1.0.
AGPL-3.0-or-later. Copyright (c) 2026 Aliasgar Khimani (NovusEdge).
Free to clone, run, and modify, including by automated agents and harnesses. Any distributed or network-hosted derivative must publish its source under the same license, so stoat cannot be forked into a proprietary product.