Introduction
qvm-ctl (installed as qvm) is a fast, lightweight CLI tool written in Rust to manage repeatable QEMU/KVM virtual machine lifecycles without libvirt.
What It Does
qvm-ctl is a command-line tool that lets you create, run, and manage QEMU/KVM virtual machines with simple commands instead of dealing with complex hypervisors. It automatically configures UEFI boot, TPM 2.0 emulation, virtual disks, and graphical displays so modern Linux and Windows installations work immediately out of the box. Each virtual machine is kept self-contained in its own folder on your computer, making your VMs easy to locate, inspect, and delete.
Built with Rust
Written from the ground up in idiomatic Rust, qvm-ctl emphasizes speed, safety, and operational reliability:
- Zero Runtime Overhead: Compiled into a single binary with no interpreter dependencies or background hypervisor daemons.
- Reliable Process Lifecycle: Direct OS signal dispatch and process supervision via native system libraries.
- Deterministic Port & Concurrency Management: Built-in TCP socket probing and cross-process file locking guarantee conflict-free multi-VM execution.
Next Steps
To get started, follow the Installation guide to install the required system packages and set up the qvm command on your system. Next, check out the Getting Started walkthrough to create and boot your first VM, or jump into the Command Reference for full command syntax. You can also explore Configuration to tweak hardware defaults, or visit the GitHub repository to view the source code and report issues.
Installation
1. Install Dependencies
Ensure virtualization is supported (ls -l /dev/kvm). Then install dependencies for your distribution (including the Rust toolchain to compile from source):
Arch Linux
sudo pacman -S qemu-full edk2-ovmf swtpm virt-viewer freerdp rust
Debian / Ubuntu
sudo apt update
sudo apt install qemu-system-x86 qemu-utils ovmf swtpm swtpm-tools virt-viewer freerdp2-x11 cargo rustc
Fedora
sudo dnf install qemu-kvm qemu-img edk2-ovmf swtpm swtpm-tools virt-viewer freerdp cargo rust
Note: If you get permission errors accessing
/dev/kvm, add your user to thekvmgroup:sudo usermod -aG kvm $USER
2. Build & Install qvm
Clone the repository and build the release binary with cargo:
git clone https://github.com/HenriqueStelzer/qvm-ctl.git
cd qvm-ctl
cargo build --release
sudo install -m755 target/release/qvm /usr/local/bin/qvm
Alternatively, install directly using cargo install:
cargo install --path .
Or on Arch Linux using the included PKGBUILD:
makepkg -si
3. Verify
qvm version
# Output: qvm 1.2.0
Getting Started
Follow these 4 steps to create and run your first virtual machine.
1. Create a VM
Run qvm create <name> <iso> [virtio_driver_iso]:
# Linux VM
qvm create my-arch ~/Downloads/archlinux-x86_64.iso
# Windows VM (with optional VirtIO driver ISO)
qvm create win11 ~/Downloads/Win11_x64.iso ~/Downloads/virtio-win.iso
This creates a dedicated directory at ~/vms/<name>/ with a 40GB qcow2 virtual disk and UEFI NVRAM.
2. Boot with Installer ISO
Start the VM for installation:
qvm launch win11
- QEMU and TPM 2.0 (
swtpm) start in the background. - If
remote-viewerorvirt-vieweris installed, the graphical display opens automatically. - Otherwise, connect with any SPICE client:
remote-viewer spice://localhost:5900.
3. Boot from Disk (Post-Install)
Once OS installation is complete, shut down the VM. For future boots, start directly from disk without attaching the installer ISO:
qvm launch win11 --no-iso
4. Manage Lifecycle
# View all VMs and their status
qvm list
# Gracefully shut down a running VM
qvm stop win11
# Delete a VM and remove its files (requires typing VM name to confirm)
qvm disable win11
Command Reference
Quick summary of all qvm subcommands.
Commands
| Command | Usage | Description |
|---|---|---|
create | qvm create <name> <iso> [driver_iso] | Provision a new VM directory, disk, and configuration |
launch | qvm launch <name> [--no-iso] [--headless] | Start VM with or without installation media |
app | qvm app <name> <app_path> [args...] | Run individual guest app via FreeRDP RemoteApp |
stop | qvm stop <name> | Gracefully shut down a running VM (ACPI SIGTERM) |
list | qvm list | Display all VMs and their running/stopped status |
disable | qvm disable <name> | Stop and permanently delete a VM |
version | qvm version | Print qvm version number |
help | qvm help | Display built-in CLI help |
Global Tips
- Color Output: Automatically disabled when stdout is piped or when
NO_COLOR=1is set. - Exit Status: Exits
0on success and1on error with error details printed tostderr.
qvm create
Creates and initializes a new virtual machine.
Usage
qvm create <name> <iso> [driver_iso]
Arguments
<name>: Alphanumeric VM identifier (can include-and_).<iso>: Path to the installer ISO image.[driver_iso](optional): Path to auxiliary driver ISO (e.g. VirtIO drivers for Windows).
What It Creates
Stores everything under ~/vms/<name>/ (override via QVM_DIR):
vm.conf: Saved VM hardware configuration.disk.qcow2: QCOW2 sparse virtual disk (default: 40G, override viaQVM_DISK).ovmf-vars.fd: Dedicated writable UEFI NVRAM.
Examples
# Basic VM
qvm create debian-vm ~/Downloads/debian.iso
# Windows VM with VirtIO drivers
qvm create win11 ~/Downloads/Win11.iso ~/Downloads/virtio-win.iso
# Custom RAM, CPU, and Disk size
QVM_RAM=16384 QVM_CPUS=8 QVM_DISK=80G qvm create dev-box ~/Downloads/arch.iso
qvm launch
Boots a virtual machine in the background and connects to its display.
Usage
qvm launch <name> [--no-iso] [--headless]
Flags
--no-iso: Boots from the virtual disk only, without mounting installation or driver ISOs. Use this for normal day-to-day boots after OS installation.--headless: Runs the VM in the background without automatically opening a SPICE display viewer (remote-viewer/virt-viewer). SPICE and RDP services remain accessible over their respective ports.
What It Does
- Locks the VM to prevent concurrent duplicate launches.
- Allocates the next available SPICE port (starting at 5900) and saves it to
spice.port. - Allocates the next available RDP port (starting at 3389) and saves it to
rdp.port. - Starts an isolated TPM 2.0 emulator daemon (
swtpm). - Launches QEMU with KVM acceleration, UEFI firmware, VirtIO hardware, and user networking with RDP port forwarding.
- Spawns
remote-viewerorvirt-viewerif installed (unless--headlessis passed).
Examples
# Boot with installer ISO attached (for installation)
qvm launch win11
# Boot directly from disk (post-installation)
qvm launch win11 --no-iso
# Boot directly from disk in headless mode (no GUI viewer)
qvm launch win11 --no-iso --headless
Remote / Manual Connection
If running headless or if no viewer is installed on the host:
- SPICE viewer:
remote-viewer spice://localhost:5900 - RDP / Remote Desktop:
wlfreerdp /v:127.0.0.1:3389 /u:User
qvm app
Launches an individual application inside the guest VM and displays it as a seamless, native window on the host desktop using FreeRDP and Windows RemoteApp (RAIL).
Usage
qvm app <name> <app_path> [args...]
Arguments
<name>: The VM name.<app_path>: The full path to the executable inside the guest (e.g.C:\Program Files\Notepad++\notepad++.exeornotepad.exe).[args...]: Optional arguments to pass to the guest application.
What It Does
- Checks if the VM is running. If stopped, automatically starts it in headless mode (
--no-iso --headless). - Discovers the VM’s assigned RDP port (from
rdp.port). - Waits for the guest RDP service to become responsive.
- Detects the best available FreeRDP client on the host (
wlfreerdpon Wayland, falling back toxfreerdp). - Resolves credentials from environment variables (
QVM_RDP_USER,QVM_RDP_PASS) orvm.conf(RDP_USER,RDP_PASS), defaulting to$USERand prompting securely if password is not preset. - Launches the application in seamless RemoteApp mode with clipboard integration and dynamic resolution matching.
Guest Setup (One-time)
To allow RemoteApp execution, configure the Windows guest:
1. Enable Remote Desktop
Open Settings → System → Remote Desktop and toggle On.
2. Allow RemoteApps
By default, Windows only permits pre-registered RemoteApps. Run the following command in an Administrator Command Prompt or PowerShell inside the guest to allow launching any unlisted executable:
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList" /v fDisabledAllowList /t REG_DWORD /d 1 /f
(Alternatively, open-source tools such as RemoteApp Tool can be used to generate specific allowed application lists).
Examples
# Launch Notepad
qvm app win11 notepad.exe
# Launch Notepad with an argument
qvm app win11 notepad.exe "C:\Users\Public\notes.txt"
# Launch an installed third-party application
qvm app win11 "C:\Program Files\Notepad++\notepad++.exe"
# Launch with credentials specified in environment
QVM_RDP_USER="Admin" QVM_RDP_PASS="secret" qvm app win11 cmd.exe
qvm stop
Gracefully powers down a running VM.
Usage
qvm stop <name>
How It Works
- Sends
SIGTERMto the QEMU process, triggering an ACPI power button event in the guest OS for clean shutdown. - Waits up to 10 seconds for the guest to cleanly shut down and flush disks.
- If the guest does not exit within 10 seconds, automatically escalates to
SIGKILL. - Saves TPM state via
swtpm_ioctl --saveand terminates theswtpmdaemon. - Cleans up PID and socket files.
Example
qvm stop win11
# Output:
# → Stopping win11...
# ✓ Stopped win11
qvm list
Shows all existing VMs, execution state, and connection details.
Usage
qvm list
Example Output
────────────────────────────────────────
my-arch stopped
win11 running spice://localhost:5900
────────────────────────────────────────
- Green
running: Process is active with its SPICE URL. - Yellow
stopped: VM is configured but not running.
qvm disable
Stops and permanently deletes a virtual machine.
Usage
qvm disable <name>
Warning: This will permanently delete the entire VM directory
~/vms/<name>/and its virtual disk.
Behavior
- Prompts you to type the VM name to confirm deletion:
Type 'win11' to confirm deletion: win11 - If the VM is currently running, stops it automatically.
- Recursively removes the VM directory (
rm -rf).
Scripted Deletion
To bypass the interactive confirmation in automated scripts, pipe the name:
echo "win11" | qvm disable win11
Configuration
Environment Overrides
Set these variables in your shell environment or prefix them to qvm create:
| Variable | Default | Description |
|---|---|---|
QVM_DIR | ~/vms | Root directory for all VM storage |
QVM_RAM | 8192 | Default RAM in MB for new VMs |
QVM_CPUS | 4 | Default vCPU count for new VMs |
QVM_DISK | 40G | Default disk size for new VMs |
QVM_RDP_USER | $USER | Default username for qvm app FreeRDP sessions |
QVM_RDP_PASS | (unset) | Default password for qvm app FreeRDP sessions |
NO_COLOR | (unset) | Set to disable ANSI color codes |
Example
# Create a VM in a custom path with 16GB RAM and 8 vCPUs
QVM_DIR=/mnt/storage/vms QVM_RAM=16384 QVM_CPUS=8 qvm create bigvm ~/iso/ubuntu.iso
Per-VM Config (vm.conf)
Each VM stores its configuration in ~/vms/<name>/vm.conf:
NAME=win11
ISO=/path/to/installer.iso
DRIVER_ISO=/path/to/virtio.iso
DISK=/home/user/vms/win11/disk.qcow2
OVMF_CODE=/usr/share/edk2/x64/OVMF_CODE_4M.secboot.fd
OVMF_VARS=/home/user/vms/win11/ovmf-vars.fd
RAM_MB=8192
VCPUS=4
CREATED=2026-09-13T18:00:00+00:00
# Optional credentials for 'qvm app'
RDP_USER=myusername
RDP_PASS=mypassword
To modify CPU or RAM for an existing VM, stop the VM and edit RAM_MB or VCPUS directly in this file. You can also persist default credentials for RemoteApp by adding RDP_USER (and optionally RDP_PASS).
Architecture & Internals
qvm-ctl is implemented in Rust as a compiled native binary. It manages virtual machines as standalone background processes without hypervisor daemons or runtime interpreter dependencies.
Rust Module Design
The codebase is organized into clean, single-responsibility modules:
cli: Command-line argument parsing and validation powered byclap.qemu: Pure, testable QEMU command builder mapping VM configuration to Q35/KVM parameters.port: Subprocess-free dynamic TCP port allocation usingstd::net::TcpListener(starting at port 5900 for SPICE and 3389 for RDP).process: Subprocess management, PID file tracking, and graceful signal dispatch vianix.ovmf: Multi-distribution discovery for UEFIOVMF_CODEandOVMF_VARSfirmware templates across Arch, Debian/Ubuntu, and Fedora.tpm: Lifecycle supervisor forswtpmsoftware TPM 2.0 daemon and Unix control sockets.config: Strongly-typed parser and serializer forvm.confkey-value pairs and environment overrides.error: Comprehensive error domain modeled withthiserrorandanyhow.
Directory Layout
Each VM is self-contained in ~/vms/<name>/:
~/vms/<name>/
├── vm.conf # VM configuration and hardware parameters
├── disk.qcow2 # Primary QCOW2 sparse virtual disk
├── ovmf-vars.fd # Independent writable UEFI NVRAM
├── qemu.log # Process stdout & stderr log
├── spice.port # Currently allocated SPICE port
├── rdp.port # Currently allocated RDP port
├── <name>.pid # QEMU process ID
├── swtpm.pid # swtpm process ID
├── swtpm.sock # Unix control socket for swtpm
└── tpm/ # Persistent TPM 2.0 state directory
Hardware Configuration
When qvm launch runs, it executes qemu-system-x86_64 with:
- Chipset:
q35withaccel=kvmand host CPU passthrough (-cpu host). - Firmware: UEFI via read-only
OVMF_CODEand per-VM writableovmf-vars.fd. - Security: Hardware TPM 2.0 emulation via
swtpmon a dedicated Unix socket (-device tpm-crb). - Graphics:
virtio-gpu-pciwith SPICE server andvdagentfor clipboard synchronization and auto-resizing. - Input: USB tablet pointer device (
-device usb-tablet) for lag-free cursor tracking. - Storage: Paravirtualized VirtIO block device (
-drive file=$DISK,if=virtio,format=qcow2). - Networking: User-mode networking with VirtIO (
-nic user,model=virtio-net-pci) with host port-forwarding for RDP.
Port & Concurrency Management
- Port Allocation: Dynamic scanning with native
std::net::TcpListener::bindeliminates external subprocess overhead (such asss) while guaranteeing no port collisions. - Launch Locking: Uses cross-process advisory locking (
fd-lock) on.launch.lockto prevent accidental concurrent execution of the same VM.
Troubleshooting
Quick solutions for common issues.
OVMF_CODE not found. Install: edk2-ovmf
Install UEFI firmware:
- Arch:
sudo pacman -S edk2-ovmf - Debian / Ubuntu:
sudo apt install ovmf - Fedora:
sudo dnf install edk2-ovmf
QEMU exited immediately. Check: qemu.log
Check the log:
cat ~/vms/<name>/qemu.log
Common fixes:
- Permission denied on
/dev/kvm: Add user to group:sudo usermod -aG kvm $USERand log back in. - VT-x / AMD-V disabled: Enable virtualization in your host motherboard BIOS/UEFI.
Windows installer cannot find the virtual disk
The installer lacks VirtIO storage drivers. Download virtio-win.iso and pass it as the third argument:
qvm create win11 /path/to/win11.iso /path/to/virtio-win.iso
In the installer, select Load driver -> browse to the VirtIO CD -> viostor/w11/amd64.
Failed to start swtpm
Install swtpm or clear orphaned sockets:
# Verify installation
command -v swtpm || sudo pacman -S swtpm
# Clear stale socket if previous run crashed
rm -f ~/vms/<name>/swtpm.sock ~/vms/<name>/swtpm.pid
VM '<name>' is already launching
If an earlier launch was interrupted, remove the stale lockfile:
rm -f ~/vms/<name>/.launch.lock
Clipboard or auto-resize not working
Install the SPICE guest agent inside the guest OS:
- Linux guest:
sudo pacman -S spice-vdagent(orsudo apt install spice-vdagent) - Windows guest: Run
virtio-win-guest-tools.exefrom the VirtIO driver ISO.