Create and manage VMs¶
Create VMs directly from an image URL and drive their full lifecycle
with the otherix vm CLI (the control-plane /v1/vms surface). VMs
are name-keyed: every command takes the VM name as its positional
argument; UUID literals are rejected by the server with
400 validation_failed.
There is no template entity. A VM is created from an image reference; the agent owns a per-pool image cache and materializes the URL on first use.
Create¶
otherix vm create web-1 \
--image-url https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-arm64.img \
--arch arm64 \
--vcpus 2 --memory-mb 2048 \
--wait
vm create is async: it submits a vm.create task and prints the
task id immediately. --wait blocks until the task reaches a terminal
state.
Flags¶
--image-url and --arch are the only required flags.
| Flag | Default | Notes |
|---|---|---|
--image-url |
- | Source image URL to download and boot from. Required. |
--arch |
- | amd64 or arm64. Required. |
--image-sha256 |
(unset) | Expected sha256; verified after download. |
--firmware |
(unset) | Firmware name. Mutually exclusive with --firmware-id. |
--firmware-id |
(unset) | Firmware uuid. Mutually exclusive with --firmware. |
--format |
(server default) | Disk format, e.g. qcow2 or raw. |
--disk-gib |
(image virtual size) | Root disk size in GiB. |
--pool |
(cluster default) | Storage pool name or uuid. When omitted the server resolves the cluster default-pool; if none is set it returns 400 default_pool_not_set. |
--node |
(scheduler picks) | Placement hint - node name or uuid. Mismatch (pool not on that node) returns 409 pool_not_on_node. |
--network |
(unset) | Bridge network name or uuid to attach one NIC. Non-bridge types return 400. When omitted the VM has no NIC and the agent falls back to SLIRP networking. |
--vcpus |
2 |
vCPU count (1..128). |
--memory-mb |
2048 |
Memory in MiB (128..524288). |
--user-data |
(unset) | Path to a #cloud-config user-data YAML, or - for stdin. Mutually exclusive with --no-cloud-init. |
--network-config |
(unset) | Path to a cloud-init network-config YAML (netplan v2), or - for stdin. Mutually exclusive with --no-cloud-init. |
--no-cloud-init |
false |
Explicitly disable cloud-init. Mutually exclusive with --user-data and --network-config. |
--wait |
false |
Block until the task reaches terminal status. |
--wait-timeout |
5m |
Max wait when --wait is set. |
When --pool is omitted the cluster default-pool reference is used and
the scheduler picks the (node, pool instance) target. A --node hint
pins placement to exactly that node.
Inspect¶
# List VMs visible to the caller. UUIDs hidden by default.
otherix vm list
# Reveal the UUID column.
otherix vm list --show-ids
vm list is cursor-paginated (--limit, --cursor) and filters
server-side with --pool, --node (by agent-reported current
location, not pinned intent), and --status. Output: --output
(-o) accepts table (default), json, or yaml.
# Single VM by name.
otherix vm get web-1
# Round-trip a manifest you can re-apply with `otherix create -f`.
otherix vm get web-1 -o yaml
vm get defaults to a multi-line key/value view; -o json echoes the
raw server envelope; -o yaml projects a declarative manifest. (Some
create-time-only fields do not round-trip through -o yaml - keep your
source manifest as the record of what you applied.)
Lifecycle¶
Mutating operations split into sync (the call returns the refreshed
VM directly) and async (the call returns a task id; pass --wait
to block).
| Command | Mode | Effect |
|---|---|---|
vm start <name> |
async | Boot a stopped VM. Sets desired_phase=running. Idempotent. |
vm stop <name> |
async | Graceful ACPI shutdown (QMP system_powerdown). On timeout the task fails with stop_timeout. |
vm stop <name> --force |
async | CLI-level dispatch to poweroff (hard shutdown, guest not notified). |
vm poweroff <name> |
async | Hard power-off (QMP quit, then SIGKILL). Sets desired_phase=stopped. |
vm reboot <name> |
async | Graceful stop+start cycle; the QEMU PID changes. |
vm reset <name> |
sync | QMP system_reset - the reset button. Runtime identity preserved. |
vm pause <name> |
sync | QMP stop - vCPUs freeze, memory stays resident. |
vm resume <name> |
sync | QMP cont - continue a paused VM. |
vm delete <name> |
async | Tear down and soft-delete. Prompts for confirmation unless --force. |
Sync commands (pause, resume, reset) accept --output
(text/json) and return the refreshed VM view; re-issuing an op the
VM is already in (e.g. pausing a paused VM) returns 409 conflict.
Async commands (start, stop, poweroff, reboot, delete)
accept --wait and --wait-timeout (default 5m).
otherix vm stop web-1 --wait
otherix vm stop web-1 --force # hard poweroff
otherix vm reboot web-1 --wait
otherix vm delete web-1 --wait --force
The async task model¶
Async operations return 202 Accepted with a task id rather than
blocking. The client polls GET /v1/tasks/{id} until the status
reaches success, failed, or cancelled.
The CLI does this for you with --wait: it prints one dot per poll to
stderr (so stdout stays parseable) and exits non-zero on a failed task,
surfacing the task error as task <id> failed: <code>: <message>.
Without --wait the command prints <op> task=<id> status=<status>
and returns immediately - poll the task yourself, or re-run the command
with --wait.