Lightweight KVM virtual machine manager for Linux®
Provides a CLI and web UI for creating, running, and managing QEMU/KVM virtual machines with cloud-init support.
- Create VMs from cloud images (qcow2/img) or ISO installers, downloaded or supplied from your own disk
- Thin-provisioned disks (copy-on-write clones of base images)
- Cloud-init for automatic SSH key injection, hostname, and root/default-user password
- Auto-generated root password per VM, revealable from the web UI
- Serial console access (CLI and web terminal via xterm.js)
- User-mode (NAT) and bridged networking
- Web UI with real-time VM management
- Configurable image registry (add your own images)
- Graceful shutdown cleans up tap devices on exit
- Linux with KVM support (
/dev/kvm) - QEMU (
qemu-system-x86_64,qemu-img) genisoimageormkisofs(for cloud-init ISO generation)- Go 1.26+ (to build from source)
- npm to install xterm.js
Optional (for bridged networking):
dnsmasqiptables- Root privileges
git clone https://github.com/c0m4r/v.git
cd v
./addons/npm.sh # fetches xterm.js into cmd/web/static/vendor/
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.version=$(cat VERSION)" -buildvcs=false -o v .Cross build:
./addons/build.shv can run either as an unprivileged user or as root. The two setups differ in networking capabilities and data location:
| User mode (no root) | Bridge mode (root) | |
|---|---|---|
| Setup | ./v serve |
sudo ./v net setupsudo ./v serve |
| Networking | QEMU user-mode NAT | v-br0 bridge + dnsmasq DHCP + iptables NAT |
| VM IPs | none (guest sees 10.0.2.15 internally) | real IPs on 10.10.10.0/24 |
| SSH access | host port forwardingssh -p 2222 localhost |
direct to guest IPssh root@10.10.10.x |
| Root required | no | yes — for bridge, taps, iptables, dnsmasq |
| Data dir | ~/.local/share/v/ |
/root/.local/share/v/ |
| Create flag | --net user (default) |
--net bridge |
./start.sh is a convenience wrapper that sets up bridge networking if missing and launches v serve under sudo.
Note on the data directory: Because the default data path is derived from
$HOME, user-mode VMs live under your home directory while root-mode VMs live under/root. VMs created in one mode are not visible from the other. Override the location with theV_DATA_DIRenvironment variable if you want a shared path.
Dashboard will be available at http://127.0.0.1:8080 once v serve is running.
# Pull a cloud image
v image pull ubuntu-24.04
# Create a VM (root password is auto-generated and printed)
v create --name myvm --image ubuntu-24.04 --memory 1024 --cpus 2
# Start it
v start myvm
# Connect via SSH (user-mode networking)
ssh -p 2222 ubuntu@localhost
# Or attach to the serial console
v console myvm # Ctrl+] to detach| Command | Description |
|---|---|
v create --name NAME --image IMAGE [options] |
Create a new VM |
v list |
List all VMs |
v info <name|id> |
Show VM details |
v start <name|id> |
Start a VM |
v stop <name|id> |
Graceful ACPI shutdown |
v force-stop <name|id> |
Kill VM process immediately |
v restart <name|id> |
Restart a VM |
v delete <name|id> |
Delete a VM (must be stopped) |
v console <name|id> |
Attach to serial console |
v set-boot <name|id> <disk|cdrom> |
Change boot device |
| Flag | Default | Description |
|---|---|---|
--name |
(required) | VM name |
--image |
(required) | Base image name or cached filename (unless --iso is used) |
--iso |
Path to a local ISO/disk image on this machine; links it into the cache and installs from it | |
--cpus |
1 | Number of vCPUs |
--memory |
512 | Memory in MB |
--disk |
10G | Disk size |
--net |
user | Network mode: user or bridge |
--ssh-key |
SSH public key (string or path to .pub file) | |
--password |
(auto-gen) | Root/default-user password; none to disable password auth |
--user-data |
Path to custom cloud-init user-data file (overrides --ssh-key and --password) |
The password is applied to both root and the distro default user (ubuntu, debian, rocky, …) via cloud-init.
| Command | Description |
|---|---|
v image pull <name|url> |
Download a cloud image |
v image import <path> [--name NAME] |
Register a local ISO/disk image (linked, not copied) |
v image list |
List cached images and where they came from |
v image available |
Show all known images and their URLs |
| Command | Description |
|---|---|
v disk create --path PATH --size SIZE [--backing FILE] |
Create a standalone disk image |
| Command | Description |
|---|---|
v net setup |
Set up bridge networking (requires root) |
v net teardown |
Remove bridge and NAT rules (requires root) |
v net status |
Show network infrastructure status |
Bridge networking creates a v-br0 bridge with subnet 10.10.10.0/24, runs dnsmasq for DHCP, and configures NAT via iptables.
| Command | Description |
|---|---|
v config |
Show current configuration |
v config set ssh-key <key|path> |
Set default SSH key for new VMs |
| Command | Description |
|---|---|
v serve [--listen ADDR] |
Start the web UI (default: 127.0.0.1:8080) |
v version |
Show version |
v help |
Show usage |
Start the web server:
v serve
# or bind to a specific address
v serve --listen 0.0.0.0:9090The web UI provides:
- VM list with state, IP, and action buttons (start/stop/force-stop/restart/delete/console)
- VM creation dialog with image selection (cached + available for download + a custom ISO from the host's disk), auto-generated root password with regenerate button, and a "no password" option
- Per-VM password reveal dialog (show/hide/copy the stored root password)
- Live serial console via WebSocket (xterm.js) with fullscreen toggle
- Console controls are disabled when the xterm.js assets were not included in the build
- Bridge network option is automatically disabled if v is not running as root
- Settings management (default SSH key) and a Shutdown button for gracefully stopping
v serve
On shutdown (Ctrl-C or SIGTERM), the web server cleans up all v-tap-* interfaces it created.
You can boot VMs from ISO installer images (e.g., Alpine Linux, any OS installer):
# Pull an ISO image
v image pull alpine-v3.23
# Create a VM from it (creates blank disk, skips cloud-init)
v create --name alpine --image alpine-virt-3.23.3-x86_64.iso --disk 5G
# Boot from ISO and install via console
v start alpine
v console alpine
# After installation, switch to booting from disk
v stop alpine
v set-boot alpine disk
v start alpineWhen a VM is created from an ISO:
- A blank disk is created (no backing file)
- Cloud-init is skipped (installers don't use it)
- Boot device is set to
cdromautomatically - On start, the ISO is attached as CDROM with boot priority
Any ISO already on the machine running v can be used as install media — nothing is downloaded:
# One step: link the ISO into the cache and create the VM from it
v create --name win11 --iso ~/Downloads/Win11_24H2.iso --disk 40G --memory 4096 --gpu virtio
v start win11
v console win11 # or use the GUI window with --gpu virtio
# After installation, boot from the installed disk instead
v stop win11
v set-boot win11 disk
v start win11Or import first and reuse the ISO for several VMs:
v image import ~/Downloads/Win11_24H2.iso
v image list
# NAME SIZE SOURCE
# Win11_24H2.iso 5721.4 MB linked -> /home/you/Downloads/Win11_24H2.iso
v create --name win11 --image Win11_24H2.iso --disk 40G.iso, .img, .qcow2, and .raw files are accepted. Notes:
- The file is linked, not copied. The cache entry is a symlink, so importing a 5 GB ISO costs no extra disk space — but moving or deleting the original breaks every VM using it.
v image listflags such entries asbroken link, andv startreports which path went missing. - Characters that aren't valid in a cache filename are replaced with
-, soMy Windows 11 (x64).isois cached asMy-Windows-11-x64-.iso. Use--nameto choose the cached name yourself. - The ISO must be readable by the user running
v. Remember thatsudo vruns as root with/root/.local/share/v/as its data dir.
In the web UI, pick Custom ISO from disk... at the bottom of the image dropdown in the Create VM dialog and enter the path. The path is resolved on the machine running v serve, not in the browser — there is no upload, so any file readable by the v process can be attached this way. Keep that in mind before exposing v serve beyond 127.0.0.1.
Images are configured in ~/.local/share/v/config.json. Built-in defaults are always available; user entries are merged on top (and can override defaults):
{
"images": {
"my-distro": "https://example.com/my-distro-cloud.qcow2",
"ubuntu-24.04": "https://my-mirror.example.com/ubuntu-noble.img"
}
}You can also pull any image by URL directly:
v image pull https://example.com/some-image.qcow2| Name | Format |
|---|---|
ubuntu-24.04 |
qcow2 (cloud image) |
ubuntu-22.04 |
qcow2 (cloud image) |
debian-13 |
qcow2 (cloud image) |
debian-12 |
qcow2 (cloud image) |
alpine-v3.23 |
ISO (installer) |
rocky-10 |
qcow2 (cloud image) |
VM data, images, and configuration are stored under $HOME/.local/share/v/ — which means the location depends on which user runs v:
- Run as your user →
~/.local/share/v/ - Run as root (directly or via
sudo) →/root/.local/share/v/
Override the location entirely with the V_DATA_DIR environment variable.
<data-dir>/
config.json # user settings and custom images
dnsmasq.pid # dnsmasq PID (bridge mode)
dnsmasq.leases # DHCP leases (bridge mode)
dnsmasq.log # dnsmasq log (bridge mode)
images/ # cached base images
vms/
<vm-id>/
vm.json # VM metadata (includes stored root password)
disk.qcow2 # VM disk (thin clone or blank)
cloud-init.iso # cloud-init data (cloud images only)
pid # QEMU process ID (when running)
qmp.sock # QMP control socket (when running)
console.sock # serial console socket (when running)
No root required. QEMU provides built-in NAT. SSH access is via port forwarding on the host (starting at port 2222, auto-incremented per VM).
v create --name myvm --image ubuntu-24.04 # --net user is the default
v start myvm
ssh -p 2222 ubuntu@localhostRequires root. VMs get real IPs on a shared bridge network (10.10.10.0/24). IPs are assigned via dnsmasq DHCP and visible in v info / v list.
sudo v net setup # one-time bridge/NAT/DHCP setup
sudo v create --name myvm --image ubuntu-24.04 --net bridge
sudo v start myvm
sudo v info myvm # shows assigned IP
ssh ubuntu@10.10.10.xOr, for the web UI:
sudo v net setup
sudo v servev net setup creates v-br0, assigns 10.10.10.1/24, enables IP forwarding, adds iptables MASQUERADE + FORWARD rules for the default route interface, and starts dnsmasq. The bridge persists after VMs stop or the web UI shuts down, including when launched through start.sh.
After stopping any bridge VMs, run sudo v net teardown to remove the bridge, leftover v-tap-* interfaces, and v's NAT/forwarding rules, and stop dnsmasq. Teardown reports cleanup failures and can be retried; it also removes rules for an uplink that has changed since setup. Host IP forwarding remains enabled.
./addons/check.shSee LICENSE file.
Linux® is the registered trademark of Linus Torvalds in the U.S. and other countries.