This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Documentation

Install Vmango, connect your libvirt hosts and launch virtual machines.

Vmango manages virtual machines across multiple local or remote libvirt hosts from one web interface. Configure your hypervisor, storage pools and networking first, then connect Vmango to launch and manage machines from your browser.

Get started

The example configuration documents settings for hosts, images, authentication and event hooks.

Explore the interface

Choose a namespace and cloud, then review machine states, configured resources and IP addresses in that workspace’s inventory. Filter by host or state, and open a machine to inspect its settings or connect to its console. These screenshots use demonstration data; select a screenshot to view it at full size.

Vmango machine inventory with state filters, resource summaries and IP addresses.

Machine inventory and resource summaries.

The creation form shows the CPU, memory and boot disk included with the selected instance type before you launch a machine.

Machine creation with an instance type providing 4 vCPUs, 4 GiB of memory and a 40 GiB boot disk.

Choose an image, instance type, network and SSH keys. Demonstration data.

A cloud configuration example

Replace the cloud "local" block in the quick-start configuration with this example, keeping its image and API settings. It connects a local Linux/KVM host and offers two instance types. It uses the existing ubuntu-24.04-x86_64-bios image definition and assumes an active libvirt network and a writable storage pool, both named default.

# Replace the quick-start cloud block; retain its nested images and API settings.
cloud "local" {
    name = "Local cloud"
    # Keep the nested image definitions from vmango.quickstart.conf here.

    zone "local" { name = "Local zone" }
    network "lan" { name = "Private LAN" }
    storage_class "standard" { name = "Standard storage" }

    instance_type "small" {
        name = "Small"
        cpu = 2
        memory_mib = 2048
        boot_disk_gib = 20
    }

    instance_type "medium" {
        name = "Medium"
        cpu = 4
        memory_mib = 4096
        boot_disk_gib = 40
    }

    host "local" {
        uri = "qemu:///system"
        config_drive_pool = "default"
        zone = "local"
        cpu_reserve = 1
        cpu_overcommit = 4
        memory_reserve_mib = 1024
        network "lan" { network = "default" }
        storage_class "standard" { pool = "default" }
    }
}

Download the cloud configuration example

Continue with configured clouds and scheduling to add hosts, choose resource reserves and configure placement rules.

1 - Binary quick start

Download Vmango, connect a libvirt host and open the web interface.

Download an archive and SHA256SUMS from GitHub Releases. Choose the Vmango server’s architecture, which may differ from the guest VM’s architecture. No Docker or Go compiler is needed.

To run a container instead, follow Docker installation.

Optional web setup

On macOS and Linux, vmango install --config /absolute/path/vmango.conf starts a temporary loopback wizard for preparing Linux hosts locally or over SSH. It requires a separately supplied pinned Richt executable. Review OS changes, network/storage changes and the final configuration independently; the wizard does not start or restart the Vmango server. See the installer guide for the exact runtime build, prerequisites, permissions and recovery steps. Local macOS hypervisor provisioning and Windows controllers are outside this wizard’s scope. The manual setup below remains available.

Choose an archive

Replace VERSION below with the version shown on the release, without its v prefix.

ArchiveRuntime
vmango-VERSION-linux-x64.tar.gzx86_64 Linux with glibc 2.35 or newer; built on Ubuntu 22.04
vmango-VERSION-macos-arm64.tar.gzApple Silicon, macOS 15 or newer, Homebrew in /opt/homebrew
vmango-VERSION-windows-x64.zip64-bit Windows; CI runs on Windows Server 2022; connects to a remote libvirt host

These are dynamically linked applications. The Windows ZIP includes libvirt DLLs, their dependencies, virsh.exe, license notices and a package manifest. Keep them next to vmango.exe. Linux and macOS use system-installed libvirt libraries. The archives include a minimal configuration and this guide.

Linux prerequisites

On Ubuntu 22.04 or newer:

sudo apt-get update
sudo apt-get install libvirt0 libvirt-clients genisoimage openssh-client

genisoimage supplies the mkisofs and isoinfo commands used to create and read cloud-init config drives. Other distributions need equivalent packages; Alpine’s musl runtime is not supported by this Linux archive.

macOS prerequisites

Install Homebrew, then:

brew install libvirt dvdrtools

The binary links to Homebrew’s libvirt in /opt/homebrew. dvdrtools provides mkisofs and isoinfo. This does not configure a local hypervisor: use a remote Linux/KVM host for this quick start. Local QEMU/HVF needs separate setup. The binary is not Developer ID signed or notarized; follow your organization’s policy for downloaded executables.

Windows prerequisites

Extract the complete ZIP. The bundled libvirt client does not run a Windows hypervisor. Use a remote Linux/KVM host with authenticated TLS (qemu+tls). Follow libvirt’s TLS setup and Windows client certificate locations. Do not substitute an unauthenticated public TCP connection.

The bundled Windows client cannot use SSH: qemu+ssh is unsupported on Windows, and the Fedora MinGW libvirt build has both qemu+libssh and qemu+libssh2 disabled. Installing OpenSSH or adding a libssh2 DLL does not enable these transports. CI probes all three URIs with the packaged virsh.exe; using SSH would require a different libvirt build and a separate connection test.

The archive does not include ISO tools. Creating or reading cloud-init config drives requires Windows-compatible mkisofs.exe and isoinfo.exe on PATH. Until those are installed, use Windows for managing existing VMs; provisioning with config drives is not covered by the Windows CI smoke check. Event hook scripts also require a sh executable. See the verification scope below.

Verify and unpack

On Linux, from the directory containing the archive and checksums:

sha256sum --ignore-missing -c SHA256SUMS
tar -xzf vmango-VERSION-linux-x64.tar.gz
cd vmango-VERSION-linux-x64
cp vmango.quickstart.conf vmango.conf

On macOS:

shasum -a 256 vmango-VERSION-macos-arm64.tar.gz
# Compare the result with this archive's line in SHA256SUMS.
tar -xzf vmango-VERSION-macos-arm64.tar.gz
cd vmango-VERSION-macos-arm64
cp vmango.quickstart.conf vmango.conf

On Windows, in PowerShell:

Get-FileHash .\vmango-VERSION-windows-x64.zip -Algorithm SHA256
# Compare the result with this archive's line in SHA256SUMS.
Expand-Archive .\vmango-VERSION-windows-x64.zip -DestinationPath .
Set-Location .\vmango-VERSION-windows-x64
Copy-Item vmango.quickstart.conf vmango.conf
# Make the bundled virsh and any tools in this directory available by name.
$env:PATH = "$PWD;$env:PATH"

Connect a hypervisor

For Linux running on an already configured KVM host, verify access as the same user who will run Vmango:

virsh -c qemu:///system list --all

For Linux or macOS connecting to a remote host, first configure SSH key access and confirm the host key interactively. The remote account must have permission to use the libvirt system socket:

ssh libvirt@hypervisor.example.com
virsh -c 'qemu+ssh://libvirt@hypervisor.example.com/system' list --all

Use that URI in vmango.conf. On Windows, test the configured TLS connection with the bundled client:

.\virsh.exe -c 'qemu+tls://hypervisor.example.com/system' list --all

Edit the archive’s vmango.quickstart.conf and save it as vmango.conf. Keep its shipped namespace, image and cloud blocks. The excerpt below shows only connection and login settings; it is not a complete cloud configuration:

key_file = "./authorized_keys"

namespace "platform" {
    name = "Platform"
    clouds = ["local"]
}

cloud "local" {
    # Keep the shipped images, zone, instance types, network and storage class.
    host "local" {
        uri = "qemu:///system"
        config_drive_pool = "default"
        zone = "local"
        # Keep the shipped reserves, network and storage bindings here.
    }
}

api {
    listen = "127.0.0.1:8080"
    base_url = "http://localhost:8080"
    jwt_signing_key_file = "~/.vmango/jwt_signing_key"

    user "admin" {
        hashed_password = "REPLACE_WITH_GENPW_OUTPUT"
    }
}

Replace uri inside cloud "local" → host "local" with the verified URI. The host name local is its stable identity; connection and backend settings belong directly in that host block. Use an existing, active storage pool for config_drive_pool. For example, check virsh -c URI pool-list --all.

HTTPS

For access from other computers, configure HTTPS either directly in Vmango or through an HTTPS reverse proxy. Native HTTPS uses the existing api.listen address; edit the api block in your configuration:

api {
    listen = ":8443"
    base_url = "https://vmango.example.com:8443"
    tls_cert_file = "/etc/vmango/tls/fullchain.pem"
    tls_key_file = "/etc/vmango/tls/privkey.pem"
    # Preserve your users, signing-key paths and credential-store settings.
}

tls_cert_file contains the PEM server certificate followed by its intermediate certificates, and tls_key_file contains its matching unencrypted PEM private key. Use a certificate valid for the hostname clients connect to and trusted by their browsers. Both files must be readable by the process user (vmango for RPM/DEB services); restrict access to the private key. Absolute paths are recommended; ~/ paths are expanded using the process user’s home directory.

Setting both fields enables HTTPS only on that address, with TLS 1.2 or newer. There is no separate HTTP listener or automatic redirect. Leaving both empty preserves HTTP. Setting just one field, or supplying unreadable, malformed or mismatched files, prevents startup. Port 8443 works without privileged-port permissions in packaged services.

Secure authentication cookies and console wss:// connections are automatic with native HTTPS; session_secure = true is not required. If base_url is set, it must be an absolute HTTPS URL. Set it to the public URL, including a nonstandard port, for OIDC callbacks and absolute shared console links.

Certificate issuance and renewal are managed externally. Vmango loads the certificate and key once at startup; replace them and restart Vmango after renewal. For a packaged service, use systemctl restart vmango.

Behind an HTTPS reverse proxy, leave the TLS file fields empty to keep the upstream listener on HTTP, set base_url to the public HTTPS URL and enable session_secure. Bind the upstream to loopback or a private network. The proxy must preserve the public Host, Origin and Sec-Fetch-Site headers and support WebSocket upgrades.

Manage multiple hosts

One Vmango instance can manage virtual machines across multiple local or remote libvirt hosts from the same web interface. Add a host block inside the cloud for each hypervisor. Host names and connection URIs must be globally unique. The following replaces the cloud "local" block in the quick-start configuration; keep its nested image definitions:

cloud "local" {
    # Keep the nested image definitions from vmango.quickstart.conf here.
    zone "local" {}
    instance_type "small" { cpu = 2 memory_mib = 2048 boot_disk_gib = 20 }
    network "lan" {}
    storage_class "standard" {}

    host "local" {
        uri = "qemu:///system"
        config_drive_pool = "default"
        zone = "local"
        network "lan" { network = "default" }
        storage_class "standard" { pool = "default" }
    }
    host "remote" {
        uri = "qemu+ssh://libvirt@hypervisor.example.com/system"
        config_drive_pool = "default"
        zone = "local"
        network "lan" { network = "default" }
        storage_class "standard" { pool = "default" }
    }
}

This example connects a Linux Vmango server to its local KVM host and a remote host over SSH. Use connection types supported by your server platform; Windows requires remote hosts with authenticated TLS, as described above. Verify each URI and its storage pool as the user running Vmango. Pool names refer to pools on each respective host; they do not create shared storage. Each host contains its connection settings, AZ, logical networks and storage classes together. Multiple hosts can share one AZ. Restart Vmango after changing the configuration. The scheduler selects the host when creating a VM; the user does not select a node. See configured clouds and scheduling.

Set a password and start

Run ./vmango genpw (PowerShell: .\vmango.exe genpw), enter your administrator password, and paste the printed bcrypt hash into hashed_password without changing its $ characters. Run this in an interactive terminal.

API JWTs and console links share one Ed25519 signing key, stored as an unencrypted PKCS#8 PEM file with mode 0600. The default is ~/.vmango/jwt_signing_key; override it with api.jwt_signing_key_file. A missing file or contents that cannot be decoded as PEM, including an old hex secret, is replaced automatically at startup. This invalidates old signed tokens and console links. An unsupported or invalid key in a decodable PEM file causes a startup error and is left untouched. Preserve the private file across restarts. Remove the obsolete api.console_signing_key_file and any old inline key settings from configuration. Start from the extracted directory, which must be writable:

./vmango web -c vmango.conf

On Windows:

.\vmango.exe web -c vmango.conf

Open http://localhost:8080/ and log in as admin with the password you just entered. Choose Platform in the Namespace selector, then select the local cloud. Read-only host diagnostics are available under Administration → Clouds → local → Hosts. Stop the foreground server with Ctrl+C.

A namespace groups your machines, disks, keys, templates and tasks into a workspace. The shipped configuration allows Platform to use the local cloud. See Namespaces to add workspaces or use multiple clouds. Existing machines and disks need explicit assignment after an upgrade; find them in Administration and follow the guide’s migration instructions.

To create a VM, first check the cloud catalog and host bindings, then follow the cloud images and provisioning guide to use the shipped lazy downloadable catalog or register a manually prepared source volume on the hypervisor in vmango.conf. Administrators prepare the hypervisor’s pools and networking and configure private host bindings. Users then create machines from catalog references; they do not select raw pools or image paths. The binary archive does not configure KVM, pools or networks for you; a missing downloadable image is prepared when an accepted create task needs it.

For remote browser access, use native HTTPS or an HTTPS reverse proxy and set api.base_url to the public URL. Behind a proxy, enable api.session_secure and preserve Host, Origin and Sec-Fetch-Site. Remote VNC access needs HTTPS too. SSH console forwarding also needs ssh locally and socat or nc on the hypervisor. TLS-connected consoles require network reachability to the host’s VNC port; a successful libvirt TLS connection alone does not tunnel VNC.

Troubleshooting empty clouds or hosts

  • Select a namespace and confirm its clouds list includes the desired cloud. An empty selector requires a namespace block in the configuration and a restart. Existing unassigned machines remain visible in Administration inventory.
  • Check the startup log for the configuration filename. Use -c explicitly if the current directory differs from the extracted archive.
  • Define at least one host "name" inside a cloud, including its uri. Hosts are configured explicitly, not discovered automatically.
  • Read cannot get node / cannot open libvirt connection messages. A failed connection is omitted from the node list.
  • Run virsh -c 'EXACT_URI' list --all as the same user and with the same environment. Fix socket permissions, SSH keys, host-key trust or TLS certificates there first. A remote account may need membership in the host’s libvirt group and a new login session.
  • Define a cloud catalog and put each connection’s settings in its host block, including its AZ and network/storage bindings. Top-level libvirt blocks must be migrated into cloud hosts.
  • Verify the pool exists and is active on that connection. An empty VM list is normal on a new host and does not mean that no nodes are configured.

Verification scope

CI builds each archive, extracts it and checks server startup, login and a node using libvirt’s test:///default driver. Unit tests run on Linux, macOS and Windows. This checks dynamic loading and the HTTP path, not a real hypervisor. Linux and macOS CI also checks an ISO round trip with the installed tools. Windows config-drive tools and real Windows-to-Linux TLS/VM operations require separate integration verification before claiming those deployment scenarios as tested. A successful macOS check does not establish Linux/KVM behavior. Verify VM lifecycle operations on a real hypervisor before release. See the release process.

2 - Docker installation

Run the Vmango Docker image on Linux amd64 or arm64 and connect existing libvirt hosts.

The subuk/vmango image on Docker Hub includes Vmango, its web interface, libvirt clients, SSH, CA certificates and the ISO tools used for cloud-init config drives. It connects to existing hypervisors; it does not start a libvirt daemon or configure a KVM host.

Choose an image

docker pull subuk/vmango:0.15.1

The examples below pin release 0.15.1. Use a published version from Docker Hub when upgrading.

TagContents
0.15.1Release image for Linux amd64 and arm64; Docker selects the matching platform
latestLatest stable release, with both Linux platforms
0.15.1-amd64Explicit Linux amd64 image
0.15.1-arm64Explicit Linux arm64 image

Prereleases have version-specific tags and do not update latest. The image’s architecture is the Vmango server’s architecture; guest VM architectures depend on the connected hypervisors and configured images.

Prepare the configuration

Export the example configuration from the same image you will run. Start in a new directory so the command does not overwrite an existing vmango.conf:

mkdir vmango-docker
cd vmango-docker
docker run --rm --entrypoint cat subuk/vmango:0.15.1 /etc/vmango.conf > vmango.conf
docker run --rm -it subuk/vmango:0.15.1 genpw

Enter an administrator password in the interactive terminal. In vmango.conf, add a user "admin" block inside the existing api block and paste the printed bcrypt hash into hashed_password. Preserve its $ characters.

For deployment behind an HTTPS reverse proxy, keep api.listen = ":8080" and set api.base_url to your public HTTPS URL. The following shows the relevant API settings; edit the existing block rather than appending a second one:

api {
    listen = ":8080"
    base_url = "https://vmango.example.com"
    session_secure = true
    jwt_signing_key_file = "/var/lib/vmango/jwt_signing_key"
    session_dir = "/var/lib/vmango/sessions"
    token_dir = "/var/lib/vmango/tokens"

    user "admin" {
        hashed_password = "REPLACE_WITH_GENPW_OUTPUT"
    }
}

Keep the shipped image, cloud and tasks blocks and the state paths under /var/lib/vmango. Configure the existing storage pools and networks for each host, following configured clouds and scheduling. The image guide explains the managed image catalog.

To terminate HTTPS in Vmango instead, set tls_cert_file and tls_key_file inside the existing api block and mount their containing directory read-only into the container, for example -v "$PWD/tls:/etc/vmango/tls:ro". Use container paths such as /etc/vmango/tls/fullchain.pem and /etc/vmango/tls/privkey.pem in the configuration. The existing :8080 listener then serves HTTPS only; the published port mapping can stay the same. Set base_url to the public HTTPS URL with its external port. Secure cookies and console WSS connections are automatic. Files must be readable by the container’s Vmango user. After externally renewing the certificate, restart the container to reload it. See native HTTPS configuration.

Connect a libvirt host

Remote hosts over SSH

Inside the existing cloud "local" → host "local" block, replace the URI:

uri = "qemu+ssh://libvirt@hypervisor.example.com/system"

The SSH account must have permission to use libvirt on that hypervisor. Prepare a dedicated SSH directory with its private key, public key and a known_hosts file containing the verified hypervisor host key. Mount that directory read-only at /var/lib/vmango/.ssh when starting the container:

--mount type=bind,src=/absolute/path/to/vmango-ssh,dst=/var/lib/vmango/.ssh,readonly

The image runs as UID/GID 10001:10001. Its user must be able to traverse the mounted directory and read the key, SSH configuration and known_hosts. Keep private keys restricted to their owner. Set ownership on a dedicated copy of these files rather than changing your personal SSH directory. Use an SSH config file in that directory if the key has a non-default name or the host uses a custom SSH port. Passphrase-protected keys require a separately configured SSH agent. Do not disable host-key checking.

Test the connection with the same mounted SSH directory before starting Vmango:

docker run --rm \
  --mount type=bind,src=/absolute/path/to/vmango-ssh,dst=/var/lib/vmango/.ssh,readonly \
  --entrypoint virsh subuk/vmango:0.15.1 \
  -c 'qemu+ssh://libvirt@hypervisor.example.com/system' list --all

A local Linux libvirt socket

For qemu:///system on the Docker host, mount its libvirt socket directory at /run/libvirt and grant the container user the socket’s numeric group ID. For example, add these options to the container command, replacing LIBVIRT_GID with that group ID:

--mount type=bind,src=/run/libvirt,dst=/run/libvirt,readonly \
--group-add LIBVIRT_GID

The socket and any libvirt access policy must allow that group to connect. Verify the URI with virsh using the same mount and supplementary group. This example is for a Linux host; Docker Desktop does not expose a macOS libvirt socket automatically. Use a reachable remote host for that setup.

Start behind an HTTPS proxy

Create a named volume for application state, then run Vmango. Add the SSH mount or libvirt socket options from the previous section to match your host URI.

docker volume create vmango-data
docker run -d --name vmango --restart unless-stopped \
  -p 127.0.0.1:8080:8080 \
  --mount type=bind,src="$PWD/vmango.conf",dst=/etc/vmango.conf,readonly \
  --mount type=volume,src=vmango-data,dst=/var/lib/vmango \
  subuk/vmango:0.15.1
docker logs vmango

Point an HTTPS reverse proxy on the Docker host at http://127.0.0.1:8080. It must preserve the public Host, Origin and Sec-Fetch-Site headers and support WebSocket upgrades for the serial and VNC consoles. Open the public HTTPS URL from api.base_url and sign in with the configured administrator.

Docker forwards a non-loopback peer address to the container, so the API’s localhost HTTP exception does not apply even when the published host port is bound to 127.0.0.1. Configure both api.base_url with an HTTPS URL and api.session_secure = true; use the proxy for browser access. If the proxy runs in another container, put both on a private Docker network and proxy to http://vmango:8080 instead of the proxy container’s own localhost.

The named volume stores the signing key, SSH key records, sessions, API-token metadata and durable task/image/volume metadata. VM disks and downloaded cloud images remain in the storage pools on the hypervisors.

Upgrade and troubleshoot

Back up vmango.conf and vmango-data. Pull the desired version, stop and remove only the application container, then recreate it with the new image and the same configuration, data volume and connection mounts. Keep the named volume across upgrades so credentials and task history survive.

SymptomCheck
Login or API returns 403 transport_rejectedHTTPS proxy, public api.base_url and api.session_secure
State or signing-key permission errorWrite access to the data volume for UID/GID 10001:10001
SSH host cannot connectKey permissions, verified known_hosts, host URI and remote libvirt permissions
Local libvirt socket cannot connectSocket mount, numeric supplementary group and host access policy
Host is unavailable or resources are missingvirsh from the same container environment; existing active pools and networks

Release checks build and start both images on native runners, test JWT login, the embedded UI, libvirt’s test driver and signing-key persistence across restart. Real KVM/HVF lifecycle operations and production SSH/TLS connections need separate integration checks.

3 - Namespaces

Organize machines, disks, keys and templates into workspaces, and choose where to work.

A namespace is a named workspace for a group of virtual machines and their resources. For example, use platform for shared services and analytics for data-processing workloads. Each workspace has its own machines, volumes, SSH key catalog, cloud-init templates and workload tasks.

A cloud describes where machines can run: its hosts, images, instance types, networks and storage classes. A namespace selects which configured clouds its workloads may use. Two namespaces can share the same cloud and physical hosts; one namespace can use several clouds.

For example, Platform and Analytics can both run machines in the local cloud, while Platform also uses edge. Selecting Analytics shows its workloads, even when Platform machines are running on the same host. Both workspaces may have a machine called web; Vmango keeps their backend identities distinct.

Namespaces organize resources and prevent operations from accidentally combining resources from different workspaces. Every authenticated user can currently select every configured namespace. Namespaces do not provide per-user access permissions, separate guest networks or CPU/storage quotas. Hosts and their available capacity remain shared.

Configure a namespace

The installation administrator adds namespace blocks to vmango.conf, alongside the existing cloud and API blocks. For a single workspace using an existing cloud "local", start with:

namespace "platform" {
    name = "Platform"
    description = "Shared services"
    clouds = ["local"]
}

The shipped quick-start configuration already contains this namespace; edit it instead of adding another block with the same ID. Even a single-workspace installation needs an explicit namespace.

SettingMeaning
"platform"Stable namespace ID used in links, API requests and saved resource assignments. Keep it when changing the display name.
nameName shown in the selector. Optional; defaults to the ID.
descriptionOptional explanation for operators.
cloudsOne or more existing cloud IDs this workspace may use. IDs must be unique in the list.

To add Analytics on the same cloud, append:

namespace "analytics" {
    name = "Analytics"
    clouds = ["local"]
}

If cloud "edge" is also configured, Platform can use both with clouds = ["local", "edge"]. This list refers to cloud IDs, not host names. Restart Vmango after editing the configuration. Namespace creation and changes are performed in the configuration file; the web interface displays them under Administration → Namespaces.

Changing the name changes the displayed name. Changing the block’s ID creates a different workspace; it does not rename or move existing resource assignments.

Work in a namespace

  1. Sign in and choose Platform in the Namespace selector. If the URL has no namespace, Vmango asks you to choose one, including on single-workspace installations.
  2. On Machines or Volumes, choose a Cloud from those allowed for the workspace. If there is only one available choice, it may already be selected.
  3. Add provisioning keys under Keys and reusable user data under Cloud-init templates. These catalogs belong to the namespace and can be used in any of its allowed clouds. Add a key or template separately in another namespace when it should also be available there.
  4. Select Create machine and follow the provisioning guide. The new machine and its disks belong to the selected namespace. Its display name may match a name in another namespace. The optional guest hostname is configured separately from that display name.
  5. Use Tasks to follow work accepted in this namespace. Switching namespace changes the task list; already accepted operations retain their original namespace and continue there.

Switch workspaces using the Namespace selector. Machine and disk lists, keys, templates and tasks then use the new workspace. Save or discard unfinished form edits when prompted; changing workspaces also disconnects an embedded console. To attach a disk to a machine, both must belong to the same namespace and satisfy the selected cloud’s placement rules. Selecting another namespace does not transfer a machine or disk between workspaces.

The current tab’s URL records its workspace, for example:

https://vmango.example/machines/?namespace=platform

Copy the full URL when sharing a workload page. Reload and Back/Forward restore the namespace from that URL. You can keep Platform open in one tab and Analytics in another; their selections are independent. A missing namespace prompts for a choice. An unknown or malformed namespace shows an error instead of silently opening a different workspace.

Find and assign existing resources

After upgrading an older installation, existing VMs and disks have no namespace until explicitly assigned. They are visible in Administration → Clouds → your cloud → Machine inventory / Volume inventory, even if workload lists are empty. Administration also shows resources belonging to other namespaces and provides Task history across namespaces. Its inventories do not depend on the current workspace selection.

For an individual unassigned resource:

  1. Open its details in the Administration cloud inventory.
  2. Choose a Destination namespace, then Open assignment preview.
  3. Select Preview assignment and review the machines and disks together. Assigning a VM includes its attached disks and VM-owned artifacts. A graph containing resources already assigned to another namespace is rejected.
  4. Select Assign to platform (the button uses your destination’s ID), then Track task. Wait for successful completion before using the resource in the destination workspace.

Assignment does not move disk data or grant Vmango permission to delete a disk that it did not create or adopt under its existing deletion-safety rules. Resources already assigned to a namespace cannot be transferred to another namespace through this workflow.

For an installation upgrade or bulk assignment, use the migration and recovery guide. It covers stopping Vmango, backing up state, previewing an explicit mapping, applying it and recovering interrupted work. Existing SSH keys and templates must be explicitly selected for migration; they are not automatically copied into every workspace. API clients must also send the new namespace header.

Common questions

Why is my cloud missing? Check the selected namespace’s clouds list and restart Vmango after changing it. Adding a namespace does not configure hosts, networks or storage pools; see cloud configuration.

Why is my machine missing? Check the Namespace and Cloud selectors, then use Administration inventory to inspect its assignment. Machines created outside Vmango or before the upgrade may still be unassigned. A warning about unavailable hosts means the inventory may be incomplete.

What happens if I remove a namespace block? It disappears from the selector after restart and cannot accept new work. Its machines, disks, keys, templates and history remain stored. Administration still shows the saved namespace ID. Restore the same ID with the required clouds to use it again. Removing a block is not a resource-deletion operation.

What must I back up? VM assignments are stored in libvirt domain XML; volume assignments, keys and templates are stored on the Vmango application host beneath namespace_directory (default ~/.vmango/namespaces). Preserve both along with the rest of the installation state. Follow the backup and restore procedure before moving the server or restoring an installation.

How do scripts choose a workspace? Discover configured choices with GET /api/v1/namespaces, then send X-Vmango-Namespace: platform on workload requests. The browser’s ?namespace= convention is not an API query parameter. See the API guide.

4 - Package installation

Install release packages on Ubuntu, Debian and RHEL.

Use the release repositories below. Configure /etc/vmango.conf for your libvirt host and web login before starting the service. For a container deployment with bundled dependencies, see Docker installation.

Ubuntu 22.04, 24.04 and 26.04 LTS (amd64)

sudo add-apt-repository ppa:subuk/vmango
sudo apt-get update
sudo apt-get install vmango

The release PPA contains separate Jammy, Noble and Resolute builds.

Debian 12 and 13 (amd64)

Use the Ubuntu 22.04 Jammy build from the release PPA. These are Ubuntu packages, not native Debian builds. The package validation workflow checks installation, server startup, login and a libvirt test node on Debian 12 (Bookworm) and Debian 13 (Trixie). Real KVM operations require separate testing.

Add only the Vmango PPA, with its signing key restricted to this repository:

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
curl -fsSL 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x10538B83BC93D600E0C98A39AF0F396262965789' \
  -o /tmp/vmango-ppa.asc
gpg --show-keys --with-fingerprint /tmp/vmango-ppa.asc

Verify the primary key fingerprint is 1053 8B83 BC93 D600 E0C9 8A39 AF0F 3962 6296 5789, also shown on the PPA overview. Then install the key and add the repository:

sudo install -d -m 0755 /etc/apt/keyrings
sudo install -m 0644 /tmp/vmango-ppa.asc /etc/apt/keyrings/vmango-ppa.asc
printf '%s\n' \
  'Types: deb' \
  'URIs: https://ppa.launchpadcontent.net/subuk/vmango/ubuntu' \
  'Suites: jammy' \
  'Components: main' \
  'Architectures: amd64' \
  'Signed-By: /etc/apt/keyrings/vmango-ppa.asc' \
  | sudo tee /etc/apt/sources.list.d/vmango.sources
sudo apt-get update
sudo apt-get install vmango

Keep Suites: jammy on both Debian versions. Do not replace it with bookworm or trixie, and do not add Ubuntu’s main repositories to Debian. The Go compiler and compiler PPA are not needed to install binary packages.

RHEL 8/9/10 and CentOS Stream 10 (x86_64)

On RHEL 8/9 or CentOS Stream 10, enable the release repository and install the package:

sudo dnf install -y dnf-plugins-core
sudo dnf copr enable subuk/vmango
sudo dnf install vmango

On RHEL 10, use an explicit target when enabling the repository:

sudo dnf copr enable subuk/vmango rhel-10-x86_64
sudo dnf install vmango

The EPEL 10 build uses CentOS Stream and requires a newer libvirt than the RHEL 10 build. Select rhel-10-x86_64 on RHEL 10 to use its matching dependencies.

The COPR plugin normally selects an epel-* repository. Both EPEL and RHEL repositories are published for versions 8, 9 and 10 on x86_64.

TargetBuild environment
rhel-8-x86_64, rhel-9-x86_64, rhel-10-x86_64RHEL without EPEL build dependencies
epel-8-x86_64, epel-9-x86_64RHEL + EPEL
epel-10-x86_64CentOS Stream + EPEL

To use a RHEL build explicitly, append the matching target to the enable command, for example sudo dnf copr enable subuk/vmango rhel-8-x86_64. Use rhel-9-x86_64 or rhel-10-x86_64 for RHEL 9 or 10 respectively.

See the release COPR project for completed builds and repository setup for your system.

Configure and start

Edit /etc/vmango.conf, configure a password and connect a libvirt host as described in the quick-start guide. Package installation does not configure a hypervisor, storage pools or VM images.

On systems where the package installs the systemd unit:

sudo systemctl enable --now vmango

5 - Cloud images and provisioning

Find cloud images, register them in Vmango and launch a virtual machine.

Vmango starts machines from pre-installed cloud images. There is no need to boot an installer or mount an installation CD for each VM: cloud-init handles the initial configuration on first boot.

Image entries are exposed to users through a configured cloud. Define the cloud catalog and host bindings before creating machines.

Cloud-owned downloadable catalog

vmango.dist.conf ships twelve pinned QCOW2 definitions: Ubuntu 24.04 LTS, Debian 13 Generic, Fedora Cloud Base 44, AlmaLinux GenericCloud 9.8, Rocky Linux GenericCloud 9.8 and Alpine 3.24.3 cloud-init VM images, each for x86_64/BIOS and aarch64/UEFI. The Alpine images use Limine; the selected boot mode is explicit in each catalog ID and label. These settings describe the selected variants, not universal BIOS/UEFI or cross-architecture compatibility. The selector filters architecture and launch requirements using node capabilities.

Each image entry belongs inside its owning cloud and is available on compatible nodes of that cloud. Equal image IDs in different clouds are independent. Set image_pool directly in each existing cloud host. This excerpt shows the image storage settings; keep the cloud’s catalog and each host’s zone, network and storage-class bindings:

cloud "local" {
    # Keep the cloud catalog here.
    host "local" {
        uri = "qemu:///system"
        image_pool = "default"
        # Keep this host's zone and network/storage bindings.
    }
    host "remote" {
        uri = "qemu+ssh://libvirt@example.org/system"
        image_pool = "cloud-images"
        # Keep this host's zone and network/storage bindings.
    }
}

image_pool names an existing dir libvirt storage pool on that node and defaults to default. The pool must be active, writable through libvirt, and have enough free space for the artifact. Vmango does not create, start or mount pools. Other pool types, including fs, netfs, LVM and RBD, are not supported for managed image storage. VM destination disks use a logical storage class mapped to a pool on the selected host. The same catalog entry can use different pool paths on different nodes. Adding a node needs only a cloud host block with its connection and pool binding, without copying definitions within that cloud.

A downloadable entry has url, mandatory sha256 and source_format (raw or qcow2), with no path. Use the complete definitions in vmango.dist.conf as ready-to-use examples. SHA-256 covers the exact downloaded file bytes, not a guest-visible filesystem or an unpacked disk. Add the archive settings below when the download contains a disk inside ZIP or TAR. External QCOW2 backing/data files, encryption and incompatible QCOW2 features are rejected.

Select a disk inside an archive

Keep url, sha256 and source_format, and add archive_format and archive_member to select exactly one regular file:

image "archived-linux" {
    os_arch = "aarch64"
    url = "https://images.example.org/linux.tar.gz"
    sha256 = "REPLACE_WITH_ARCHIVE_SHA256"
    source_format = "qcow2"
    archive_format = "tar.gz"
    archive_member = "images/system.qcow2"
    extracted_size_bytes = 2147483648
}

Replace the URL, checksum, member name and size with the published artifact’s values. archive_format accepts zip, tar and tar.gz. source_format remains raw or qcow2 and describes the selected disk, not its container. Both archive settings must be present together; omitting both retains direct-file downloads. Local path entries do not use archive settings.

archive_member is the exact, case-sensitive path stored in the archive, using forward slashes. Use a relative path without a drive prefix, leading slash, backslashes, empty components, . or ... Vmango does not extract a directory or follow symbolic/hard links: the selected member must be a single regular file. Missing or duplicate matching members fail preparation. ZIP supports stored and Deflate members, including ZIP64 and data descriptors; encrypted members and unsupported compression methods are rejected. TAR supports plain or gzip-compressed archives.

The sizes describe different things:

SettingMeaning
sha256SHA-256 of the complete downloaded archive
source_size_bytesOptional exact archive file byte count; required if HTTP omits Content-Length
extracted_size_bytesOptional exact selected file byte count after decompression
minimum_disk_gibMinimum guest-visible boot disk capacity

Set extracted_size_bytes when known so placement can budget the actual image storage. Without it, placement conservatively reserves image_transfers.max_bytes for an unprepared archived image. A configured extracted size must agree with the archive member; it is not the QCOW2 virtual capacity.

ZIP preparation makes two complete HTTP requests. The first verifies the archive SHA-256 and reads its bounded central-directory metadata; the second streams the selected disk to libvirt and verifies the full archive again before completing the upload. ZIP metadata must fit in a 4 MiB tail window with at most 10,000 entries. Split archives and nonstandard footer gaps are rejected. This handles members whose local headers omit their sizes without keeping the archive on disk. TAR and TAR.GZ use one HTTP pass; after the selected member, Vmango scans the remainder before completing the upload to detect duplicates, validate trailers and verify the complete download checksum. Nonzero data after the TAR end marker is rejected; concatenated TAR archives are not supported.

image_transfers.max_bytes bounds the downloaded archive and the extracted disk file separately. For TAR and TAR.GZ it also bounds the complete TAR stream after decompression, including headers and skipped files. An archive may therefore exceed the limit even when the selected disk fits. Checksum, ZIP CRC, gzip trailer, size or format failures leave the image unverified and use the staging cleanup described below. Archive payloads and extracted disks are never written to temporary files on the Vmango application host. Only the selected disk is uploaded to the target pool.

Lazy preparation and progress

Configuration parsing, startup and discovery make no upstream image requests. A missing compatible image remains selectable. The first accepted machine.create prepares it on the selected node and then automatically continues creating the VM. Closing the form or browser does not cancel accepted work.

The data path is upstream HTTP(S) → Vmango → libvirt volume upload. Download and upload overlap with bounded buffers and backpressure for direct files and TAR; ZIP first makes the verification pass described above. Vmango creates no image payload file, temporary download, spool or application-side cache. The staging volume and reusable verified image belong to the destination libvirt node, even when Vmango and libvirt run on the same computer. Concurrent creates share one preparation for the same connection, pool and artifact. Followers remain queued while that artifact is in use by an active create, leaving workers available for unrelated images and nodes. Different nodes prepare independently. Different image IDs can share bytes while supplying different launch defaults. Archive format and selected member are part of the prepared image identity, so different disks in one archive cannot reuse each other.

Task progress shows source inspection, destination preparation, concurrent Download and upload image, final verification and normal VM provisioning. Transfer progress includes forwarded bytes, percentage, average throughput and estimated transfer time remaining. Speed includes destination backpressure; ETA covers the transfer stage and excludes verification and VM creation. Estimates are hidden when progress updates become stale.

The artifact byte size comes from Content-Length. If a source cannot provide it, set source_size_bytes in the image definition. An error names this setting when needed. This is the exact file byte count, separate from the QCOW2 virtual capacity and minimum_disk_gib. If both HTTP and configuration provide sizes they must agree. Truncated/oversized input and checksum mismatches fail the task. QCOW2 virtual capacity and safety checks use a bounded header; source format and capacity are confirmed from the resulting libvirt volume before cloning.

Optional application-wide limits:

image_transfers {
    max_bytes = 17179869184 # 16 GiB per download, extracted disk or decompressed TAR scan
    timeout_seconds = 3600
    idle_timeout_seconds = 120
}

Verification, failures and pinned updates

Managed names identify the SHA-256 and format; archived sources also include the archive format and member selection in their identity. Incomplete volumes are hidden from ordinary volume selection and cannot be cloned through public volume commands. Readiness is published only after consuming the complete response, checking SHA-256, successfully finalizing the libvirt stream and confirming backend format, capacity and identity. Durable verification metadata is stored in a private <tasks.directory>-images directory; keep it alongside task history when backing up the application. It contains metadata only. Reuse checks pool UUID, volume key, modification/change timestamps, format and capacity. A matching path alone is never adopted. External modification invalidates readiness. Removing a managed source makes it downloadable again; the next explicit create verifies a fresh transfer.

Ordinary transfer failures attempt to delete only staging created by that operation, recording confirmed effects and cleanup errors. A verified source is retained if later VM provisioning fails. Shutdown/interruption retains uncertain staging for inspection. Stale running tasks become interrupted; they are never automatically resumed or replayed. Before retrying, inspect the failed task’s exact resources and backend state, remove confirmed unused incomplete storage if necessary, and explicitly release uncertain task locks through the task UI. Never remove storage used by a VM or unrelated pre-existing volumes.

A new create after confirmed cleanup can retry explicitly; waiting requests see the active preparation’s failure. There are no background retries, upstream polls, automatic image updates or automatic deletion of old revisions. Updating an image requires an explicit configuration edit of URL/checksum together. Accepted commands retain their image definitions and pool identities, and existing VM disks stay unchanged. Old source revisions remain protected and hidden until an administrator deliberately handles them.

Publisher provenance and maintainer refresh

The source verification record, docs/data/image-sources.json, contains each official direct URL, publisher checksum URL/algorithm/digest, the calculated literal SHA-256, observed HTTP byte count/type and inspected QCOW2 virtual capacity. Where publishers supply only SHA-512 (Debian and Alpine), the artifact was streamed through SHA-512 and SHA-256 together, checked against the publisher SHA-512, and the calculated SHA-256 was pinned in configuration. No image payload was saved during that verification.

To refresh intentionally:

  1. Select a maintained release/build and architecture from the official publisher. Prefer a dated/build-specific artifact over a latest alias. Confirm its boot variant, cloud-init support and minimum resources from publisher documentation.
  2. Update that entry’s official artifact/checksum URLs and expected publisher digest in docs/data/image-sources.json. Remove its old sha256 for an intentional revision change. Obtain manifests over HTTPS and verify publisher signatures where available using trusted distribution keys.
  3. Run python3 scripts/verify-image-sources.py --record. This explicit maintainer command streams each artifact without saving payloads, checks publisher digests, inspects the header/HTTP metadata and records SHA-256. Without --record, it verifies the pinned records without rewriting them.
  4. Copy the verified URL/SHA-256 pair into vmango.dist.conf, review firmware, initialization and resource defaults, and update documentation together.
  5. Run configuration/domain/API/UI checks. Run a real Alpine download-to-boot test as described in acceptance/README.md; record release, architecture and backend.

Source/checksum validation establishes artifact identity and format. It does not establish that a guest boots or provisions correctly. Real boot checks and their backend-specific results are recorded separately in the acceptance documentation.

Register the image

Check the publisher’s checksum, unpack the image if needed, and place it in a libvirt storage pool. Register a catalog entry in vmango.conf:

image "ubuntu-server" {
    path = "/var/lib/libvirt/images/ubuntu-cloud.img"
    name = "Ubuntu server"
    os_name = "Ubuntu"
    os_version = "24.04"
    os_arch = "x86_64"
    firmware = "efi"
    disk_bus = "virtio"
    nic_model = "virtio"
    guest_agent = true
    initialization = "nocloud"
    minimum_memory_mib = 1024
    minimum_disk_gib = 8
    protected = true
}

The block label is a stable image identifier, separate from path. name is the display name (it defaults to the identifier). os_arch must be x86_64 or aarch64. For path-based entries, the selector shows images matching the selected architecture whose source volume is visible, available and not attached to a VM.

The catalog is shared across all nodes. Vmango resolves path on the selected node and reads that volume’s actual capacity and format. A volume on another node cannot satisfy a missing source. The same path on several nodes intentionally receives the same settings; administrators must ensure its contents match the image description. Path-based entries require an administrator-provisioned source on each selected node. Downloadable entries use the lazy managed flow described above.

Several entries can reference the same disk with different defaults:

image "ubuntu-unmanaged" {
    path = "/var/lib/libvirt/images/ubuntu-cloud.img"
    name = "Ubuntu without provisioning"
    os_name = "Ubuntu"
    os_arch = "x86_64"
    guest_agent = false
    initialization = "none"
}

Selecting this identifier disables the guest agent channel and omits the config drive. It does not change the ubuntu-server entry or the shared source disk.

Creation defaults and requirements

SettingValuesWhen omitted
firmwareauto, bios, efiExisting backend firmware selection
disk_busvirtio, scsi, idevirtio
disk_controllervirtio-scsi (requires disk_bus = "scsi")Backend-selected controller
nic_modelvirtio, e1000, rtl8139, subject to backend supportvirtio
guest_agenttrue, falsetrue
graphicsnone, vnc, spice, subject to backend supportvnc
video_modelnone, cirrus, qxl, virtio (2D), subject to backend supportnone
initializationnone, nocloud, openstackSelected node’s config_drive_write_format
minimum_memory_mibNonnegative integer, MiBNo additional minimum
minimum_disk_gibNonnegative integer, GiBSource volume capacity

graphics = "none" requires video_model = "none" (or omission). The image owns these guest device settings. The graphics listener is a private graphics_listen IP address in each cloud host block, resolved after host selection; it does not belong to an image or type. Omission preserves the backend listener default.

The backend checks capabilities for the selected architecture and node. A listed option is not necessarily supported by every backend. For example, built-in QEMU user networking and socket-vmnet support only virtio NICs.

The simplified cloud form resolves launch settings from the selected image, instance type, network and storage class. It does not expose device, firmware, guest-agent or initialization overrides. Administrators configure these settings in HCL; explicit false and initialization none remain distinct from omitted values. Conflicting requirements are rejected rather than silently overridden.

Instance types set CPU, RAM and the size of a newly provisioned boot disk. Image minimum memory, minimum disk size and actual source virtual capacity are checked before cloning. An instance type with an undersized boot disk is rejected; no clone shrinks the source. Additional data disks have independent sizes. Changing a stopped VM’s instance type stays on its current host and preserves all existing disk sizes; insufficient host capacity produces an error.

Editing image policy affects future creations, not existing VMs. There is no per-machine policy override or PATCH endpoint for changing guest-agent, graphics or video settings. Explicit instance type changes apply CPU, RAM, hugepages and autostart while preserving existing guest devices, graphics listener and disk sizes. Adoption also preserves the existing VM configuration. Accepted tasks retain their policy and private binding snapshots. Restart Vmango after changing the configuration to reload the read-only catalog.

Required image data disks

An image can declare blank disks that every new VM needs. Add one or more data_disk blocks inside the image block:

data_disk "userdata" {
    size_gib = 16
}

Each label is a unique disk name within the image; root is reserved for the boot disk. size_gib is a positive integer. Vmango creates these disks automatically using the VM’s selected storage class and disk format, attached through VirtIO. The boot disk comes first, followed by image data disks in their declaration order, then any additional disks chosen by the user.

The creation form displays required image disks without edit or remove controls. API callers also receive them automatically and should not repeat them in new_disks; a user disk with the same name is rejected. Storage capacity checks include the required disks. They start blank, so the guest must partition and format them as needed.

Image disk configuration applies to new machines. It does not modify existing VMs, and accepted creation tasks retain the disk definitions from acceptance. Images without data_disk blocks keep their existing behavior.

Configuration migration and volume policies

Top-level image blocks and cloud.images reference lists are removed. Move each image into its owning cloud block. Copy formerly shared definitions explicitly into every cloud that uses them. For a path-labeled image, choose a stable catalog ID and put the former label in path:

cloud "local" {
    image "ubuntu-local" {
        path = "/var/lib/libvirt/images/ubuntu-cloud.img"
        os_name = "Ubuntu"
        os_arch = "x86_64"
        firmware = "efi"
        protected = true
    }
    # Keep this cloud's zones, types, network/storage catalogs and host bindings.
}

IDs match ^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$ and are unique within their cloud; source paths need not be unique. efi = true still maps to firmware = "efi" and efi = false maps to firmware = "auto"; conflicting settings are rejected. Omitted settings retain launch inheritance, including the target host’s config drive format. Restart to activate file edits.

Legacy image blocks used only for volume policy become top-level volume blocks:

volume "/dev/data/private" {
    protected = true
    hidden = true
}

protected prevents deletion and hidden excludes an ordinary volume from listing. Neither flag hides an image definition from Administration. Explicit volume policies retain their path semantics. Policies derived from a cloud image apply only to that cloud’s configured hosts; equal source paths in unrelated clouds do not share protection, hiding or attachment exclusions. Multiple images using one source on the same backend combine source policy (any true applies). Managed sources retain their existing physical protection independently of these configured flags.

Preserve task, artifact and volume-ownership metadata during migration. Queued tasks keep their accepted source/default snapshots and backend identities, even if the running catalog now differs. Unsafe legacy commands fail before mutation and require explicit resubmission; history and locks remain available for review.

Android ARM64 with LineageOS

The community LineageOS QEMU builds provide a complete ARM64 UEFI system disk. The v2026.10.20 prerelease targets upstream QEMU and works with macOS HVF; it does not require Android Emulator or Cuttlefish host services. The archive contains a UTM bundle, but Vmango uses its vda.qcow2 directly.

Add this definition inside the cloud that owns the ARM64 host. Vmango downloads the pinned release archive and selects LineageOS_on_arm64.utm/Data/vda.qcow2 automatically; no manual extraction, source-volume registration or local path is needed. Configure the host’s image_pool as described above.

image "android-lineage-24-arm64" {
    name = "Android / LineageOS 24 (ARM64)"
    os_name = "Android (LineageOS)"
    os_version = "24.0 / 20261020"
    os_arch = "aarch64"
    url = "https://github.com/jqssun/android-lineage-qemu/releases/download/v2026.10.20/UTM-VM-lineage-24.0-20261020-jqssun-virtio_arm64only.zip"
    sha256 = "f2754f6be04b5e47e35851586d350411cfbf516f67dd53ec4a80121ceaa4428e"
    source_format = "qcow2"
    source_size_bytes = 1119381498
    archive_format = "zip"
    archive_member = "LineageOS_on_arm64.utm/Data/vda.qcow2"
    extracted_size_bytes = 2222784512
    firmware = "efi"
    disk_bus = "virtio"
    nic_model = "virtio"
    graphics = "vnc"
    video_model = "virtio"
    guest_agent = false
    initialization = "none"
    protected = true
    minimum_memory_mib = 2048
    minimum_disk_gib = 5
    data_disk "userdata" {
        size_gib = 16
    }
}

Restart Vmango to load the catalog. Select an ARM64 host and a type with at least 2 vCPUs, 2 GiB RAM and a 5 GiB boot disk. Selecting this Android image automatically adds the required blank 16 GiB userdata disk. Android uses the cloned system disk as vda and formats the second disk, vdb, for /data on first boot. The archive’s vdb.qcow2 and efi_vars.fd are not extracted: Vmango creates the blank disk and libvirt creates fresh UEFI variables.

Open the machine’s console, select VNC, and connect to complete the LineageOS setup wizard. New ARM64 graphical VMs include USB keyboard and tablet input; video_model = "virtio" uses 2D graphics without host OpenGL acceleration. SSH keys and cloud-init user data do not configure this image.

Verified on 2026-11-03 through Vmango’s creation API on an Apple M1 Pro with QEMU 9.2.3, libvirt 11.2.0 and HVF: Vmango downloaded the release ZIP, verified both passes, selected the system disk and started a 2-vCPU/2-GiB VM with an 8-GiB boot disk and automatic 16-GiB userdata. The guest reached the LineageOS setup wizard. Display and pointer input were also checked through Vmango’s browser VNC console in the earlier local-source test. These checks do not establish Linux/KVM compatibility, accelerated graphics, audio or application compatibility.

Launch a machine

Choose a namespace before opening Create machine, then select one of its allowed clouds. Keys and saved templates come from that namespace; the new machine and its disks will belong to it.

  1. Choose a configuration. In the selected cloud, choose an image, instance type, availability zone, logical network and storage class. Valid single choices are selected automatically.
  2. Make it yours. Select SSH keys and optionally provide cloud-init user data. Optional placement labels express required constraints or soft preferences.
  3. Create and follow progress. The durable task inventories the cloud and schedules a compatible host, prepares a missing downloadable image, clones the boot disk and supplies guest initialization. The assigned host is visible read-only in machine details. The OS configures itself on first boot.

Once your hosts and images are set up, launching another machine is a single form and a click. Your infrastructure stays on your own hardware.

6 - Configured clouds and scheduling

Define a cloud catalog and automatically place machines on prepared libvirt hosts.

Vmango exposes configured clouds rather than asking users to assemble a libvirt domain. Administrators prepare hosts, pools and networks and define their private bindings. Users select an image, instance type, AZ, logical network and storage class. The scheduler assigns a host, shown read-only in the machine details.

Configure a cloud

Keep the image definitions from the shipped configuration, or define images using the image guide. Define each host’s connection and backend settings directly in its cloud. The example assumes an active libvirt network named default and a writable pool named default; Vmango does not create them.

cloud "local" {
    name = "Local cloud"
    # Keep the nested image definitions from vmango.quickstart.conf here.

    zone "local" { name = "Local zone" }
    network "lan" { name = "Private LAN" }
    storage_class "standard" { name = "Standard storage" }

    instance_type "small" {
        name = "Small"
        cpu = 2
        cpu_request = 1
        memory_mib = 2048
        boot_disk_gib = 20
    }

    host "local" {
        uri = "qemu:///system"
        config_drive_pool = "default"
        image_pool = "default"
        zone = "local"
        labels = { rack = "rack-a" }
        cpu_reserve = 1
        cpu_overcommit = 4
        memory_reserve_mib = 1024
        network "lan" { network = "default" }
        storage_class "standard" {
            pool = "default"
            format = "qcow2"
        }
    }
}

Each host owns its libvirt connection and belongs to exactly one cloud and AZ. To add another host, add a host block with its own uri, zone and network/pool bindings. Host names and connection URIs must be globally unique across clouds; several hosts can share the same AZ. Use architecture-appropriate image IDs; unsupported combinations are rejected.

Connection settings uri, image_pool, config_drive_pool, config_drive_suffix, config_drive_write_format, emulator, graphics_listen, qcow_prealloc_metadata and cache all belong directly in cloud.host. image_pool and config_drive_pool default to default, config_drive_suffix to _config.iso and config_drive_write_format to nocloud. graphics_listen defaults to the backend’s listener setting.

Migrate existing connections

Move the contents of each former top-level libvirt "name" block into host "name" in its cloud, then remove the top-level block. Preserve the exact host name, URI and pool settings so resource routes, task scopes and image ownership stay associated with the same backend. Top-level libvirt blocks and legacy libvirt_uri / libvirt_config_drive_* options now fail startup with a migration instruction. Backend settings do not belong in the public cloud API.

Resources and placement

cpu sets the actual vCPU count. cpu_request defaults to cpu, must be positive and no greater than it, and affects admission only; it does not impose quotas or CPU shares. Memory requests equal guest RAM; memory overselling is unsupported. boot_disk_gib sets the size of a newly created boot disk and must satisfy the image minimum. Additional disks have independent sizes.

Each create inventories all cloud hosts, including stopped and unmanaged VMs. Existing VMs without scheduler metadata count at their actual configured CPU/RAM. Failed inventory reads fail admission. Host reserves reduce the available budget; disk and lazy image preparation capacity are checked too. Competing provisioning and storage commands execute serially so they cannot admit the same capacity twice.

Placement is optional. Required equality labels filter candidates; preferred labels affect scoring and do not guarantee a match. Only advertised attributes are accepted. These semantics are independent of libvirt node names. With no hints, placement is automatic within the selected AZ.

Disks, interfaces and existing machines

A storage class maps to a pool on each host. The same class on two hosts does not make storage shared. A standalone disk is scheduled within its AZ and remains host-bound after detachment. A new disk for an existing machine uses that machine’s host. Selecting existing local disks during creation constrains placement to their owning host; disks from different hosts cannot be combined or silently copied.

The main Networks page lists the selected cloud’s logical network catalog, including display names and network IDs. Its cloud selector scopes the list; multiple host bindings for one logical network appear as one entry. Backend libvirt network names are shown as read-only diagnostics on each Hosts detail page and remain available through the read-only host diagnostic API under /api/v1/administration/clouds/{cloud}/hosts/{node}/networks. Administrators provision those networks outside Vmango.

Interfaces select a configured logical network, resolved on the existing host. Interface attachment accepts network (a logical network ID) and optional mac. NIC model and VLAN are selected by the administrator’s host network binding; model, vlan and other unsupported request fields are rejected. Observed interface responses retain model and vlan for diagnostics. Custom network creation remains outside this workflow.

Unmanaged VMs are visible and counted, but read-only until explicit adoption. Adoption does not change resources, power state, disks or placement, and does not claim ownership of pre-existing disks. Deleting an adopted VM preserves unowned disks. Preserve the private volume ownership metadata alongside task history.

An explicit instance type change requires a stopped managed VM, stays on its current host, applies the selected type’s autostart policy and preserves all disk sizes, applied guest devices and graphics listener bindings. If that host lacks resources, the task fails; there is no automatic migration, stop or restart. Catalog edits never silently resize or reconfigure existing machines or disks.

Set autostart in instance_type blocks (default false). Image settings guest_agent, graphics and video_model control guest integration and devices; their defaults are true, vnc and none. The cloud’s private host block sets graphics_listen as an IP address, with omission preserving the backend default. Creation’s start flag is a separate one-time action. Adoption preserves existing policy and devices, and accepted tasks snapshot policy and private host bindings. None of these five settings accepts per-machine overrides; the machine PATCH endpoint is removed and effective values remain read-only.

System disks and boot order

Vmango assigns an explicit boot priority to the system disk when creating a VM or attaching disks. The root attachment alias identifies the system disk. If there is only one ordinary disk, it can be selected without an alias. The configdrive disk contains cloud-init data and is never selected automatically as the system disk, even though it is attached through virtio.

When reattaching a system disk to a machine with other data disks, set its alias to root. Vmango does not guess which of several unlabelled disks contains an OS. Device names and PCI addresses are preserved; the system disk need not be vda.

Legacy hd/cdrom boot settings are converted on disk attachment when a system disk can be identified. An already attached installation CD keeps its configured precedence. Existing explicit bootable hard disks, direct kernel boot and legacy PXE-only or mixed PXE boot policies are preserved. With per-device priorities, a reattached system disk is added after existing optical/network boot entries when no hard disk has a priority. Newly attached installation media can be selected through the firmware boot menu or an explicit libvirt boot configuration.

Existing machines are not rewritten or restarted merely by upgrading Vmango. Disk attachment requires a stopped VM, and the updated boot order takes effect on its next start.

API and migration

Machine, disk, interface and native console operations use /api/v1/clouds/{cloud}. Create uses catalog references and an AZ without a host input. Machine URLs use the stable VM UUID. Volumes use /clouds/{cloud}/volumes/{volume} with unpadded base64url(node) + "." + base64url(path) as the compound ID. VM disk references and existing_disks: [{volume_id}] use that same ID without a separate public host field. These addresses do not move a resource. Every request validates cloud membership. All mutations use durable tasks with visible progress and diagnostics.

Volumes, like Machines, shows one selected cloud’s flat inventory. Both list APIs return {items, unavailable_hosts}; partial failures preserve successful rows and remain explicit even when every host fails. VM/volume detail provides the logical catalog needed by ordinary forms. A null catalog means supplemental discovery failed; empty arrays mean known-empty choices. Dedicated infrastructure browsing is under Administration → Clouds → Cloud; Tasks keeps its existing URLs.

The old /api/v1/nodes/..., /api/v1/hosts/..., host-segment volume routes and global /vms, /volumes and /networks endpoints are removed without aliases. Read-only administrative diagnostics use /api/v1/administration/clouds/{cloud}/hosts/.... Authentication remains installation/principal scoped. Workloads, SSH keys, cloud-init templates and workload task history require an explicit configured namespace. The same cloud may serve several namespaces; Administration inventory and history remain independent of workspace selection. See the repository namespace user guide for configuration and everyday use, or the migration guide for existing installations.

See Automation API for Swagger UI, authentication and an overview of supported operations. The repository’s API reference lists the routes, task behavior and console examples. Binary archives include QUICKSTART.md, API.md, CLOUDS.md, IMAGES.md, TASKS.md and MIGRATION.md for offline reference. They use the maintained repository guides, with internal guide links adjusted to the archive filenames.

7 - Automation API

Explore Swagger UI, authenticate API clients and automate virtual machine operations.

Vmango provides a versioned JSON API under /api/v1. It powers the web interface and is also available to scripts, CLI tools and CI jobs.

What you can automate

  • Discover configured clouds, images, instance types, availability zones, logical networks and storage classes.
  • List, create and delete virtual machines; start, reboot or power them off; change instance types and manage attached disks and network interfaces.
  • Create, clone, grow and delete volumes, and manage SSH public keys.
  • Inspect host capabilities, networks and storage pools through read-only administrative endpoints.
  • Track asynchronous tasks and connect to serial or VNC consoles over WebSocket.

Machine and volume operations are scoped to a cloud. Creation requests select catalog entries and an availability zone; the scheduler chooses the host. See configured clouds and scheduling for the resource model.

Open Swagger UI

Open /api/docs on your Vmango server, for example https://vmango.example/api/docs. You can also choose API documentation from the account menu in the web interface.

Sign in with your usual Vmango account, using a local password or your configured OIDC provider. Swagger UI uses that browser session to load the specification and authenticate requests automatically. Expand an operation to inspect its parameters, request body, responses and schemas. Try it out sends real requests to your installation, including any selected create or delete operation.

The server also exposes its OpenAPI 3.1 specification at /api/openapi.json. Fetching it requires Bearer authentication, just like protected resource endpoints. Use the specification from your running server for its supported operations and exact request formats.

Authenticate scripts and CI jobs

  1. Sign in to the web interface and open the account menu.
  2. Choose API tokens, then Create token.
  3. Give the token a name and choose its lifetime. The default is 90 days; the maximum is 365 days.
  4. Copy the token when it is shown. It is displayed only once.

Send the token in the Authorization: Bearer TOKEN header over HTTPS. For example, save the following in ~/.config/vmango/curl.conf, replacing the placeholder with your token, and restrict the file permissions to 0600:

header = "Authorization: Bearer YOUR_PERSONAL_JWT"

Then list the clouds available on your server:

chmod 600 ~/.config/vmango/curl.conf
curl --config ~/.config/vmango/curl.conf -H "X-Vmango-Namespace: platform" \
  https://vmango.example/api/v1/clouds

The same configuration can fetch /api/openapi.json. Store tokens in a protected client file, keychain or CI secret; keep them out of URLs, source control and logs. HTTP is supported only for loopback development.

Before RBAC, personal tokens can select any configured namespace. Workload requests require X-Vmango-Namespace; discover choices through authenticated GET /api/v1/namespaces without that header. They cannot manage API tokens or issue console sharing links. Logging out of the browser does not revoke them: use Revoke on the API tokens page. Replace tokens before they expire. Missing, expired or revoked credentials return 401 Unauthorized; clients must use Bearer tokens rather than Basic Auth or browser cookies for protected resource requests.

Follow operation progress

Infrastructure changes run as durable asynchronous tasks. Send a unique Idempotency-Key header with each new command. A successful submission returns 202 Accepted, a task and a status_url; poll that URL with the same Bearer authentication until the task finishes. Acceptance does not mean the operation has completed successfully.

If a submission response is lost, retry the same command with the same key to retrieve its original task. Inspect a failed or interrupted task before submitting another command. Task history survives server restarts and is available under /api/v1/tasks.

For detailed examples and contracts, see the repository’s API reference and task guide. The console WebSocket protocol is included in OpenAPI and is also available through the Console protocol link in Swagger UI. Binary release archives include API.md and TASKS.md for offline reference.

8 - Cloud-init templates

Save reusable user-data and select one template when creating a machine.

Cloud-init templates store reusable user-data for new machines. Like SSH public keys, the catalog belongs to the selected namespace and is independent of cloud. Authenticated operators can select any configured namespace until RBAC is added. Template IDs and revision checks never resolve through another namespace.

Create and use a template

  1. Open Cloud-init templates and create a template with a name, optional description and user-data text.
  2. Open Create machine and select an image that supports initialization.
  3. Select one saved template and review its contents. Select Customize to copy it into the manual editor for this machine without changing the catalog.
  4. Select SSH keys separately and create the machine.

For example, this cloud-config writes a marker on first boot:

#cloud-config
write_files:
  - path: /etc/vmango-provisioned
    permissions: '0644'
    content: |
      Provisioned with a saved Vmango template.

Select None to omit user-data, or Custom to enter user-data manually. Only one source can be selected. Images with initialization = "none" cannot use a template or custom user-data.

Vmango preserves the submitted text. It does not merge templates, substitute variables or restrict user-data to YAML; cloud-init scripts and MIME documents can also be saved. Both saved and manually entered user-data must contain at most 256 KiB of UTF-8 text without null characters. SSH keys and the hostname continue to use the existing config-drive metadata. Their interaction with explicit user-data settings is governed by cloud-init.

Selecting a saved cloud-init template and reviewing its user-data before creating a machine.

Revisions and accepted tasks

The form submits the revision that was reviewed. If another user edits the template before creation is accepted, reload and review the updated contents. Editing and deleting templates also require the current revision, preventing one user from silently overwriting another user’s changes.

When Vmango accepts a creation task, it saves a private copy of the selected user-data with that task. Later edits or deletion of the template do not change accepted tasks or existing machines. Repeating an accepted request with the same idempotency key returns its original task.

Storage

The application stores namespace templates and SSH keys beneath:

namespace_directory = "/var/lib/vmango/namespaces"

The default is ~/.vmango/namespaces; the catalog is catalogs/catalogs.json inside that directory. It must be writable by the Vmango service account. Vmango uses private directories/files and a process lock. Include the namespace directory in protected backups. Template contents may contain credentials and are stored without encryption. The old cloud_init_templates_dir remains a legacy migration source; selected entries are imported explicitly with their IDs/revisions intact. Removing a namespace from configuration preserves its saved catalog.

The catalog list omits user-data. Template detail responses require authentication and are not cacheable. User-data is excluded from public task history and is removed from private task payloads when those tasks finish.

API

Use /api/v1/cloud-init-templates to list or create templates and /api/v1/cloud-init-templates/{id} to read, update or delete one. Updates and deletions include the reviewed revision; stale revisions return HTTP 409. All requests require X-Vmango-Namespace for the selected configured namespace.

Machine creation accepts either user_data or a reference:

{
  "user_data_template": {
    "id": "b96c873d-4997-44b0-8bbd-5ed72aac4824",
    "revision": "3f94ce75-1fce-44c4-933e-c4e287ce866d"
  }
}

Supply the other required machine fields and the Idempotency-Key header as usual. The authenticated API documentation in Vmango describes the complete request schemas.

9 - Build from source

Build and run Vmango for local development.

Building requires Go 1.26 or newer, a C compiler, pkg-config and the libvirt development libraries, Python 3 and Node/npm (the pinned Node version is in ui/.nvmrc). Creating and reading cloud-init config drives also requires mkisofs and isoinfo, provided by genisoimage or compatible tools.

git clone https://github.com/subuk/vmango.git
cd vmango
make
cp vmango.dist.conf vmango.conf

Edit vmango.conf for your host and web login before starting the server. Use ./bin/vmango genpw to generate a password hash for your configured user. The shared JWT and console signing key is generated on startup when missing. The quick-start guide explains host connections, storage pools and authentication; use ./bin/vmango in place of ./vmango when following it with a source build. The example configuration documents the available settings.

./bin/vmango web -c vmango.conf

Open localhost:8080 once the server is running. Use HTTPS when exposing the web interface remotely, as described in the quick-start guide.

Linux with QEMU/KVM is the main target. Local development on macOS is also possible with QEMU/HVF; see the README for prerequisites.

Verify changes

Run make test to build the embedded browser assets and run the Go tests. Run make ui-test for frontend regression tests and generated API-type checks. Distribution source archives include ui/dist and its source/asset digests; use make UI_PREBUILT=1 to verify and build them without Node or npm. For real VM lifecycle checks, follow the acceptance testing guide. Local Go tests do not establish that real hypervisor operations work.

Code, bug reports and contributions live on GitHub. Include your Vmango version, host setup and relevant logs when reporting an issue.