21 KiB
UVM Usage Guide
This guide explains how to install, operate, and troubleshoot UVM as an operator. It covers the command-line tool, the optional FastAPI server, common scenarios, and expected limitations.
For the project architecture and contributor guidance, see DEVELOPER.md. For a concise overview, see README.md.
1. What UVM Does
UVM creates small Linux microVMs through Firecracker and KVM on one Linux host. Each VM receives:
- A unique VM ID.
- A private writable root disk copied from a shared template.
- A private IP address and MAC address.
- A TAP interface connected to a host bridge.
- A dedicated Firecracker process, API socket, log, and config file.
By default, guest networking uses:
Guest network: 10.42.0.0/24
Gateway: 10.42.0.1
Bridge: uvm0
Guest pool: 10.42.0.2 through 10.42.0.254
The default locations on the host are under /var/lib/uvm.
2. Before You Start
Host Requirements
You need:
- Linux, preferably Ubuntu or a compatible distribution.
- Python 3.12 or newer.
- KVM enabled and readable/writable at
/dev/kvm. - Root access for installation and VM lifecycle operations.
- An x86_64 host for the default guest kernel/rootfs downloads.
- Internet access if downloading Firecracker and guest assets.
Check virtualization before installing:
ls -l /dev/kvm
lscpu | grep -i virtualization
Source Checkout Setup
From the parent directory of this project:
cd uvm
./uvm.py --help
python3 -m uvm --help
To install the optional HTTP API dependencies and the console command:
cd uvm
python3 -m pip install .
uvm --help
The rest of this guide uses ./uvm.py so it works from a source checkout. If
you installed the package, replace ./uvm.py with uvm.
Privilege Rules
| Operation | Root required? |
|---|---|
install |
Yes |
create |
Yes |
stop |
Yes |
destroy |
Yes |
list |
Yes with the default root-owned registry |
ssh |
Yes when resolving a VM through the registry |
| API server that manages VMs | Yes |
The registry contains plaintext guest credentials and is mode 0600. Direct
SSH to a known guest IP does not require root.
3. Command Overview
./uvm.py --help
./uvm.py install [--force]
./uvm.py create [--cpu CPU] [--ram RAM] [--host-ip GUEST_IP] \
[--username USER] [--password PASSWORD]
sudo ./uvm.py list
sudo ./uvm.py ssh VM_OR_IP [--user USER] [--key PATH] [--insecure-host-key]
./uvm.py stop VM_ID
./uvm.py destroy VM_ID
./uvm.py --serve [--host HOST] [--port PORT]
--host-ip is a legacy CLI name. It means the guest IP address, not the
host's public or uplink address.
4. Install UVM Assets
UVM installs host packages, Firecracker, Jailer, a guest kernel, and a guest rootfs template. It requires SHA-256 checksums by default because it downloads artifacts that will be used by a privileged process.
Scenario: Strict First Installation
Obtain trusted checksums independently, then run:
cd uvm
sudo env \
UVM_FIRECRACKER_SHA256='<sha256-of-firecracker-archive>' \
UVM_KERNEL_SHA256='<sha256-of-vmlinux>' \
UVM_ROOTFS_SHA256='<sha256-of-rootfs>' \
./uvm.py install
On success, UVM prints paths similar to:
Firecracker: /var/lib/uvm/bin/firecracker
Kernel: /var/lib/uvm/images/vmlinux
Rootfs: /var/lib/uvm/images/ubuntu.ext4
The checksums and whether the installation was verified are recorded in:
/var/lib/uvm/integrity.json
Scenario: Refresh Firecracker or Guest Assets
Use --force after changing a URL, replacing an image, or rotating checksums:
sudo env \
UVM_FIRECRACKER_SHA256='<new-firecracker-archive-sha256>' \
UVM_KERNEL_SHA256='<new-kernel-sha256>' \
UVM_ROOTFS_SHA256='<new-rootfs-sha256>' \
./uvm.py install --force
--force downloads to temporary files and only replaces the existing artifact
after checksum verification succeeds.
Scenario: Local Experimentation Without Checksums
This mode is unsafe for production. It is useful only when you deliberately accept the source artifacts without checksum verification:
sudo env UVM_ALLOW_UNVERIFIED_DOWNLOADS=1 ./uvm.py install
An unverified install is marked as unverified. It cannot later become a strict trusted installation merely by setting checksum variables. Reinstall with the strict command above when you are ready to trust the host assets.
For later local-development VM creation, keep the opt-out on the command too:
sudo env UVM_ALLOW_UNVERIFIED_DOWNLOADS=1 ./uvm.py create
The same rule applies to an API server that creates VMs from an unverified
installation: start that server with UVM_ALLOW_UNVERIFIED_DOWNLOADS=1, or
perform a strict reinstall first.
Installation Failures
| Message | Meaning and action |
|---|---|
/dev/kvm does not exist |
Enable hardware/nested virtualization first. |
checksum is required |
Provide the requested UVM_*_SHA256 variable. |
SHA-256 mismatch |
Stop and obtain the correct digest or artifact URL. |
unsupported host architecture |
Use x86_64 defaults or provide compatible assets. |
default guest assets support x86_64 only |
Configure both custom kernel and rootfs URLs for ARM. |
5. Create VMs
Scenario: Create a Default VM
The default is one CPU, 512 MiB RAM, username root, and password root:
sudo ./uvm.py create
Typical output includes the VM ID, guest IP, TAP name, and SSH target:
VM created: vm-...
IP: 10.42.0.2
RAM: 512 MiB
CPU: 1.0
TAP: uvm-...
Username: root
SSH: ssh root@10.42.0.2
WARNING: the guest is using the default password 'root'. Change it promptly.
Set a different password during creation:
sudo ./uvm.py create --username root --password '<guest-password>'
--username must name an account already present in the guest image. The
password is visible in the process arguments and may be recorded in shell
history, so avoid reusing a sensitive host or service password.
Scenario: Choose CPU and RAM
sudo ./uvm.py create --cpu 2 --ram 2G
sudo ./uvm.py create --cpu 1 --ram 1024M
sudo ./uvm.py create --cpu 1 --ram 768
RAM is in MiB by default. Supported suffixes include B, K, M, G,
KiB, MiB, and GiB.
UVM enforces a minimum of 128 MiB. Firecracker uses whole vCPUs. A fractional
request such as --cpu 0.5 is accepted but rounds the VM up to one Firecracker
vCPU; it does not currently apply a host CPU quota.
sudo ./uvm.py create --cpu 0.5 --ram 512
Scenario: Request a Specific Guest IP
sudo ./uvm.py create --host-ip 10.42.0.10
The address must be inside the configured guest network, must not be the network address, broadcast address, or gateway, and must not already belong to another persisted VM.
What Happens During Create
UVM performs these operations in order:
- Validates root access, KVM, assets, and integrity state.
- Reserves a VM ID, IP, MAC, and state record.
- Creates
/var/lib/uvm/vms/<vm-id>/. - Copies the rootfs template to a private
rootfs.ext4disk. - Sets the requested account password and enables SSH password login in the copy.
- Ensures the bridge/NAT/forwarding rules exist.
- Creates a TAP device.
- Starts and configures Firecracker.
- Marks the VM as
running.
If a step fails, UVM attempts to terminate the new VMM, remove the TAP, remove the private runtime directory, and release the reservation.
6. Inspect VMs
sudo ./uvm.py list
Example:
ID IP USER CPU RAM STATUS PID
vm-0123456789abcdef... 10.42.0.2 root 1.0 512 running 12345
Status meanings:
| Status | Meaning |
|---|---|
starting |
VM creation is in progress. |
running |
Firecracker started successfully. |
stopping |
Stop is in progress. |
stopped |
VMM and TAP are gone; disk remains. |
terminating |
Destruction is in progress. |
failed |
A lifecycle operation or cleanup failed. |
dead |
Stored state claims the VM is active but its VMM process is gone. |
If a VM is failed or dead, inspect its log before deciding whether to stop
or destroy it:
sudo ls -la /var/lib/uvm/vms/<vm-id>
sudo cat /var/lib/uvm/vms/<vm-id>/firecracker.log
7. Connect with SSH
UVM provisions a password for an existing account. The default credentials are
root / root. Your guest image must include:
- An SSH server.
- The requested login user plus standard passwd and shadow files.
UVM replaces conventional RSA, ECDSA, and Ed25519 SSH host keys in the private disk during creation and removes Firecracker's publicly known demo login key.
This is applied only while creating a new VM. Existing VM disks and legacy registry entries are not changed retroactively.
Scenario: SSH as Root
sudo ./uvm.py ssh vm-<id>
ssh root@10.42.0.2
Scenario: Use a Different User and Private Key
sudo ./uvm.py ssh vm-<id> --user ubuntu --key ~/.ssh/id_ed25519
By default, UVM uses SSH StrictHostKeyChecking=accept-new and a stable
host-key alias derived from the VM ID. This lets a guest IP be reused later
without confusing it with the prior VM's known-hosts entry.
Scenario: Temporarily Bypass Host-Key Verification
Use only for troubleshooting a guest you trust:
sudo ./uvm.py ssh vm-<id> --insecure-host-key
This disables normal host-key checking for that connection.
8. Stop and Destroy VMs
Scenario: Stop a VM and Keep Its Disk
sudo ./uvm.py stop vm-<id>
Stopping terminates Firecracker and deletes the TAP device. The private root disk, config, and log remain under the VM runtime directory.
UVM does not currently offer a start command, so a stopped disk cannot be
rebooted through the public CLI yet. Keep it only if you need to inspect it or
expect a future start feature.
Scenario: Permanently Remove a VM
sudo ./uvm.py destroy vm-<id>
Destroying a VM:
- Terminates the VMM if it is alive.
- Deletes the TAP device.
- Deletes the VM runtime directory and private root disk.
- Removes the VM record from
state.json. - Releases its IP address for later allocation.
This is destructive. Back up guest data before running it.
9. Use the FastAPI Server
The API is an alternative management surface for the same local lifecycle services. It does not make UVM multi-host or safe to expose directly to the public internet.
Scenario: Start a Local API Server
Install dependencies first:
cd uvm
python3 -m pip install .
Then start the server as root so lifecycle endpoints can manipulate KVM, networking, and host files:
export UVM_API_TOKEN='replace-with-a-long-random-visible-ascii-token'
sudo env \
UVM_API_TOKEN="$UVM_API_TOKEN" \
./uvm.py --serve --host 127.0.0.1 --port 8000
Open these local URLs after it starts:
Health: http://127.0.0.1:8000/health
OpenAPI: http://127.0.0.1:8000/docs
The API token is required even for a loopback server. All VM and installation
routes require it. FastAPI's informational /docs, /redoc, and
/openapi.json endpoints are also available without a token; they do not
perform host mutations.
Scenario: Start a Remote TLS API Server
Non-loopback binds require a valid TLS certificate and key. The files must be readable by the server process:
export UVM_API_TOKEN='replace-with-a-long-random-visible-ascii-token'
export UVM_API_TLS_CERT='/etc/uvm/api-cert.pem'
export UVM_API_TLS_KEY='/etc/uvm/api-key.pem'
sudo env \
UVM_API_TOKEN="$UVM_API_TOKEN" \
UVM_API_TLS_CERT="$UVM_API_TLS_CERT" \
UVM_API_TLS_KEY="$UVM_API_TLS_KEY" \
./uvm.py --serve --host 0.0.0.0 --port 8443
For real remote use, use a certificate trusted by clients and restrict network access with a firewall or reverse proxy. Do not use a self-signed development certificate for an untrusted network.
API Authentication
Set one reusable shell variable for API examples:
export UVM_API_TOKEN='replace-with-the-server-token'
export UVM_API_URL='http://127.0.0.1:8000'
Every management request needs:
X-UVM-Token: <token>
For a TLS server, set UVM_API_URL to an https:// URL and use your CA:
export UVM_API_URL='https://host.example:8443'
curl --cacert /path/to/ca.pem \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms"
10. API Endpoint Examples
Health Check
No token is required:
curl "$UVM_API_URL/health"
Response:
{
"status": "ok"
}
List VMs
curl \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms"
Example response:
[
{
"id": "vm-0123456789abcdef",
"cpu": 1.0,
"ram_mib": 512,
"guest_ip": "10.42.0.2",
"gateway": "10.42.0.1",
"mac": "02:fc:00:00:00:01",
"username": "root",
"status": "running",
"observed_status": "running",
"pid": 12345,
"created_at": 1700000000,
"updated_at": 1700000000,
"last_error": null
}
]
Create a VM
Create a default-sized VM:
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"cpu": 1, "ram": "512", "username": "root", "password": "root"}' \
"$UVM_API_URL/vms"
Create a VM with a chosen guest address:
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"cpu": 2, "ram": "2G", "guest_ip": "10.42.0.10", "username": "root", "password": "replace-me"}' \
"$UVM_API_URL/vms"
The response is the VM record with its username, but never its password.
Omitting credentials uses root / root. Request fields are strict:
misspelled or extra fields return HTTP 422 instead of being silently ignored.
Look Up One VM
By ID:
curl \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/vm-0123456789abcdef"
By guest IP:
curl \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/10.42.0.2"
Stop a VM
curl -X POST \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/vm-0123456789abcdef/stop"
The response is the VM record with status: "stopped".
Destroy a VM
curl -X DELETE \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/vm-0123456789abcdef"
Response:
{
"id": "vm-0123456789abcdef",
"status": "terminated"
}
Install or Refresh Through the API
The server process must itself have checksum variables configured for strict installation. Start the server with those environment variables before using this endpoint.
For a deliberately unverified local-development server, the server process
must instead include UVM_ALLOW_UNVERIFIED_DOWNLOADS=1. Do not use that mode
on a host that runs untrusted workloads.
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"force": false}' \
"$UVM_API_URL/install"
Refresh all downloaded artifacts:
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"force": true}' \
"$UVM_API_URL/install"
11. API Errors
| Status | Typical meaning |
|---|---|
401 |
Missing or incorrect X-UVM-Token. |
403 |
UVM process lacks root privileges for a host mutation. |
404 |
Requested VM was not found. |
409 |
A lifecycle operation is already in progress. |
422 |
Invalid JSON body, extra request fields, or invalid UVM input. |
500 |
Host command, Firecracker, state, or unexpected operational failure. |
UVM operation errors use this envelope:
{
"error": {
"message": "human-readable explanation"
}
}
FastAPI request-schema errors use FastAPI's normal detail response format.
12. Configuration Scenarios
Scenario: Use a Different Storage Directory
sudo env \
UVM_BASE='/srv/uvm' \
UVM_ALLOW_UNVERIFIED_DOWNLOADS=1 \
./uvm.py install
All UVM binaries, images, VM runtime directories, locks, and state use the configured base path.
Scenario: Use a Different Guest Network
sudo env \
UVM_NETWORK='10.50.0.0/24' \
UVM_GATEWAY='10.50.0.1' \
UVM_BRIDGE='uvm50' \
UVM_ALLOW_UNVERIFIED_DOWNLOADS=1 \
./uvm.py install
Use the same environment values for later create, list, stop, destroy,
and server commands. Changing the network variables after VMs exist can make
their persisted state inconsistent with host networking.
Environment Variables
| Variable | Use |
|---|---|
UVM_BASE |
Base data directory; default /var/lib/uvm |
UVM_NETWORK |
Guest subnet; default 10.42.0.0/24 |
UVM_GATEWAY |
Guest gateway / bridge IP; default 10.42.0.1 |
UVM_BRIDGE |
Linux bridge name; default uvm0 |
UVM_KERNEL_URL |
Custom kernel download URL |
UVM_ROOTFS_URL |
Custom rootfs download URL |
UVM_FIRECRACKER_SHA256 |
Strict Firecracker archive checksum |
UVM_KERNEL_SHA256 |
Strict kernel checksum |
UVM_ROOTFS_SHA256 |
Strict rootfs checksum |
UVM_FIRECRACKER_BINARY_SHA256 |
Optional local binary checksum |
UVM_ALLOW_UNVERIFIED_DOWNLOADS |
Set to 1 only for local development |
UVM_API_TOKEN |
Required API token |
UVM_API_TLS_CERT |
TLS certificate for remote server binds |
UVM_API_TLS_KEY |
TLS key for remote server binds |
13. Files, Logs, and State
The default host layout is:
/var/lib/uvm/
├── bin/ # Firecracker and Jailer binaries
├── images/ # Shared kernel and rootfs template
├── vms/<vm-id>/ # Private rootfs, config, socket, and log
├── integrity.json # Artifact checksums and verification status
└── state.json # Mode-0600 VM inventory and plaintext guest credentials
Useful inspection commands:
sudo cat /var/lib/uvm/state.json
sudo ls -la /var/lib/uvm/vms
sudo cat /var/lib/uvm/vms/<vm-id>/firecracker.log
sudo cat /var/lib/uvm/vms/<vm-id>/config.json
Avoid editing state.json, VM config files, runtime directories, TAPs, or
iptables rules by hand while UVM is running. Use UVM commands or the API so
locks and cleanup rules remain correct.
state.json contains plaintext guest passwords. Keep it root-only, never copy
it into logs or bug reports, and use unique non-default passwords outside local
throwaway environments.
14. Networking Troubleshooting
Check the bridge and TAP state:
ip link show uvm0
ip addr show dev uvm0
ip route show default
Check NAT and forwarding rules:
sudo iptables -t nat -S POSTROUTING
sudo iptables -S FORWARD
Common issues:
| Symptom | First checks |
|---|---|
| Guest cannot reach the internet | Bridge is up, TAP is up, host has default route, NAT and FORWARD rules exist. |
| Requested guest IP is rejected | Confirm it is inside the configured subnet and not used, gateway, network, or broadcast. |
| Create says TAP already exists | Inspect the interface; do not delete it unless you know it is stale UVM state. |
| Existing bridge is rejected | UVM_BRIDGE points to an interface that is not a Linux bridge. |
| SSH cannot connect | Confirm guest booted, the requested user exists, the image runs OpenSSH, and the configured password is being used. |
15. Important Limitations
Keep these constraints in mind when deciding whether UVM fits a scenario:
- Firecracker is launched directly; the downloaded Jailer is not yet used.
- No cgroup CPU, memory, I/O, or process limits are applied.
- Fractional CPU requests are advisory only.
- There is no public
startorrestartcommand after a VM is stopped. - There is no DHCP, inbound port mapping, load balancing, multi-network isolation, or multi-host scheduling.
- The API uses one shared bearer token, not individual users, roles, or tenants.
- There is no guest readiness probe beyond Firecracker process state.
- UVM is not a hardened multi-tenant platform.
16. Safe Operating Checklist
Before relying on a VM for useful work:
- Use strict checksums for installed artifacts.
- Confirm
/dev/kvmand nested virtualization are available. - Confirm UVM generated unique guest SSH host keys and set a non-default password.
- Test guest egress and SSH on the actual host network.
- Back up guest data before destroy operations.
- Keep the API loopback-only unless you have a strong remote-access need.
- For remote API access, use a long random token, valid TLS, and firewall restrictions.
- Review
/var/lib/uvm/vms/<vm-id>/firecracker.logafter failed starts.
17. Get Help
./uvm.py --help
./uvm.py install --help
./uvm.py --serve --help
When reporting a problem, collect:
- The exact command or HTTP request.
- The full error message.
- Output from
sudo ./uvm.py list. - The VM Firecracker log, if a VM was created.
- The relevant
UVM_*variables, with secrets such asUVM_API_TOKENremoved.
Never include /var/lib/uvm/state.json because it contains guest passwords.