Python CLI for ephemeral libvirt/QEMU VM templates and repeatable instance workflows.
uv syncStart from the home dashboard:
uv run ephemctlBy default, the root state directory is created in the current working directory as ./ephemerals. If ephemctl finds an existing ephemerals/ or vms/ root marker in the current directory or one of its parents, it reuses that root instead. You can also override it explicitly with EPHEMCTL_ROOT=/path/to/root.
From there you can:
- create a new instance from a built-in template
- repeat the last recipe with a fresh name and management IP
- clone a saved instance recipe
- manage existing instances, including revive, activate, spice, and destroy
You can also work directly from the CLI:
uv run ephemctl templates
uv run ephemctl create headless-dev demo-headless
uv run ephemctl create browser-client demo-desktop
uv run ephemctl doctorThe core model is template-driven. Each built-in template has a profile such as headless or desktop, plus sensible defaults for CPU, memory, networking, and persistence.
The template catalog is materialized as a user-editable JSON file at <root>/recipes/templates.json. On first run, ephemctl writes the default templates there; after that you can add new templates or modify the existing ones without editing Python code.
The old worker and browser names still exist for compatibility in commands and internal state, but they are now examples of built-in templates rather than the primary UX model:
headless-devandheadless-heavyare headless templatesbrowser-clientis a desktop template
Headless templates can optionally prompt for addons like repo clone, code-server, and a Podman container.
Templates can now carry small cloud-init fragments directly in <root>/recipes/templates.json.
available_addonscontrols which interactive addons are offered in the wizardprovision.packagesadds guest packagesprovision.write_fileswrites files with simple placeholders like{guest_user},{home_dir},{workspace_dir},{lab_pubkey}, or{lab_privkey_b64}provision.root_commandsruns root-level bootstrap commandsprovision.user_commandsruns guest-user commands throughrunuser
Minimal example:
{
"custom-desktop": {
"profile": "desktop",
"description": "Desktop VM with a repo checkout",
"name": "desk-001",
"nat_network": "default",
"cpus": "2",
"memory": "4096",
"disk_size": "20G",
"mgmt_ip": "10.31.0.30",
"guest_user": "dev",
"transient": false,
"available_addons": ["repo_clone"],
"provision": {
"packages": ["firefox"],
"root_commands": ["touch /var/lib/custom-desktop.ready"]
}
}
}The built-in headless-dev, headless-heavy, and browser-client templates already use this mechanism, so the profile-specific Python code stays small and the template file carries most of the guest bootstrap intent.
- bare
uv run ephemctlopens the interactive dashboard - instance metadata is stored under
<root>/vm/instances/<name>/ - repeat and clone reuse saved recipe data, then prompt for a new name and management IP
- desktop instances use SPICE; headless instances default to transient mode