Creating Virtual Machines
A virtual machine is made from a registered image: the node gives it CPUs, memory, its own disk layered on the image, and one to four network interfaces, configures its first boot, and starts it.
Part of Compute, in early access.
Create one
sudo cenvero-str-ctl vm create --name web-01 --tenant t-acme --image img-3f9a1c2e \
--vcpus 2 --memory 2048 --disk 20 \
--network net-acme --ssh-key-file ~/.ssh/id_ed25519.pub
| Flag | Meaning | Default |
|---|---|---|
--name | Lowercase letters, digits and hyphens, 1–63 characters, not starting or ending with a hyphen. Unique within its tenant on this node | required |
--image | The base image's id | required |
--vcpus | Virtual CPUs: from 1 up to the node's CPU count | 1 |
--memory | Memory in MiB: at least 128, at most the node's memory (machines together may add up to more) | 1024 |
--disk | Disk size in GiB: at least the image's own size, at most 65536 | the image's size |
--network NET or NET=IP | Attach to a managed network, at its next free address or the one you name. Repeatable | — |
--public-ip auto or IP | An interface holding a public address your provider routes to this server. Repeatable | — |
--tenant | The customer the machine belongs to | none (an operator machine) |
--hostname | The guest's hostname | the name |
--ssh-key-file | A public key file to authorize (every non-comment line is a key). Repeatable, up to 32 keys | — |
--user-data-file | Your own cloud-init user-data | — |
--dns | A DNS server for the guest. Repeatable, up to 3 | the node's upstream resolvers |
--bandwidth-mbps | A bandwidth limit on each of the machine's interfaces | none |
--no-start | Create it stopped | starts it |
The firmware (--firmware uefi, --secure-boot, --tpm) and the room a
machine has to grow while it runs (--max-vcpus, --max-memory) are chosen at
create too: see Firmware, resizing, interfaces and the guest
agent.
A machine needs at least one --network or --public-ip, and has at most four
interfaces in all. Public interfaces come first, so the guest's first interface
is the one it is reachable on. How each kind is wired is on
Networks and public addresses.
The node also needs at least 1 GiB free in its disk pool.
A create completes, or leaves nothing behind. A request that cannot be met —
an image that is not ready, a network of another tenant, an address in use, a
quota reached — is refused before anything is made, and a failure half-way is
undone. If the machine is created but will not start, it is kept, stopped, with
the reason in state_reason; fix the cause and run vm start.
First boot
The machine's first boot is configured through cloud-init: the node attaches a
small read-only disk labelled cidata with three files, rebuilt from its
records every time the machine starts.
- meta-data: the machine's instance id, its hostname and the SSH keys you gave.
- network-config: one entry per interface, matched by its MAC address, with the interface's static address. The default route goes through the first public interface when there is one (see public addresses); otherwise through the first interface's network gateway, if that network has one. The first interface also carries the DNS servers.
- user-data: your file, passed on exactly as you wrote it. It must start with
#cloud-configor#!and be at most 64 KiB. Without one, the node sends only what sets the hostname.
cloud-init applies these once, on the machine's first boot; what the guest
changes afterwards is its own. SSH keys and user-data are kept on the node only,
readable by root; vm show reports how many keys and how many bytes of
user-data a machine has, never their content.
#cloud-config
packages: [nginx]
runcmd:
- systemctl enable --now nginx
sudo cenvero-str-ctl vm create --name www --tenant t-acme --image img-3f9a1c2e \
--vcpus 2 --memory 2048 --network net-acme --user-data-file ./www.yaml
Tenants
A machine with --tenant belongs to that customer:
- it can attach only to that tenant's networks (and an operator machine, with no tenant, only to networks without one);
- it is kept apart from every other tenant's machines and containers, on its own port, before it first runs (see Tenants);
- it cannot be created or started while the tenant is suspended, and a suspension stops it (the resume starts it again if it was running — see Lifecycle);
- the tenant cannot be deleted while it has machines.
To cap how many machines a tenant may have on this node:
sudo cenvero-str-ctl tenant quota-set t-acme --max-vms 10
cenvero-str-ctl tenant quota t-acme # max_vms and used_vms
0 means no cap. Lowering the cap never touches machines that already exist; it
only refuses the next create.
Bandwidth. --bandwidth-mbps limits each of the machine's interfaces. It is
refused for a tenant that already has a bandwidth cap: the tenant's cap applies
to its machines (see Tenants & Bandwidth).
Looking at machines
cenvero-str-ctl vm list
cenvero-str-ctl vm list --tenant t-acme
cenvero-str-ctl vm show vm-3f9a1c2e
{
"vm": {
"id": "vm-3f9a1c2e",
"name": "web-01",
"tenant_id": "t-acme",
"state": "running",
"desired_state": "running",
"state_reason": "",
"flags": [],
"vcpus": 2,
"memory_mib": 2048,
"disk_gib": 20,
"image_id": "img-3f9a1c2e",
"hostname": "web-01",
"hypervisor": "kvm",
"nics": [
{ "index": 0, "network_id": "net-acme", "ip": "10.30.0.20", "mac": "02:ce:0a:1e:00:14",
"interface": "cnv-v-3f9a1c2e0", "bandwidth_mbps": 0 }
],
"user_data_bytes": 0,
"ssh_key_count": 1,
"console_sessions": [],
"created_at": "2026-09-28T09:20:11Z",
"updated_at": "2026-09-28T09:21:02Z"
}
}
state is what the machine is doing, desired_state what you last asked for;
Lifecycle and restarts explains the states and the
flags. hypervisor is kvm, or emulated on a node without hardware
virtualization.
Changing a machine
CPUs and memory are changed with vm update, live on a running machine within
its maximums; interfaces are added and removed with vm nic add and
vm nic remove, also while it runs. Both are on Firmware, resizing, interfaces
and the guest agent, with the guest agent
(setting a password, the guest's own addresses).
Growing the disk
sudo cenvero-str-ctl vm stop vm-3f9a1c2e
sudo cenvero-str-ctl vm resize vm-3f9a1c2e --disk 40
sudo cenvero-str-ctl vm start vm-3f9a1c2e
The machine must be stopped, and a disk can only grow. The guest sees the new size on its next boot; most cloud images then grow their root file system to fit by themselves.
Deleting
sudo cenvero-str-ctl vm delete vm-3f9a1c2e --yes
A running machine is powered off at once (not shut down gracefully — stop it first if the guest should shut down cleanly), and its disk, its first-boot disk and its interfaces are removed. Its network addresses and public address are free again straight away. The disk is gone for good — there are no snapshots or backups of machine disks yet — and its space is released without being overwritten first. A delete that is interrupted can be run again; it carries on where it stopped.
Over the API
curl -k -X POST "$NODE/api/v1/vms" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{
"name": "web-01", "tenant_id": "t-acme", "image_id": "img-3f9a1c2e",
"vcpus": 2, "memory_mib": 2048, "disk_gib": 20,
"networks": [ { "network_id": "net-acme", "ip": "10.30.0.20" } ],
"ssh_authorized_keys": [ "ssh-ed25519 AAAA… [email protected]" ],
"user_data": "#cloud-config\npackages: [nginx]\n",
"dns": [ "192.0.2.53" ],
"start": true
}'
| Method | Path | Notes |
|---|---|---|
GET | /api/v1/vms | ?tenant_id= for one tenant's |
POST | /api/v1/vms | 201 with the machine. Each networks entry has network_id (optionally ip) or public_ip, and optionally bandwidth_mbps |
GET | /api/v1/vms/{id} | One machine |
DELETE | /api/v1/vms/{id} | Delete it and its disk |
POST | /api/v1/vms/{id}/resize | {"disk_gib": 40}, stopped machines only |
Changing CPUs, memory and interfaces, and the guest agent, have their calls on Firmware, resizing, interfaces and the guest agent.
Every change answers as described here and also names the task that
records it ("task", and a Location header): who asked, what was done and how
it ended — including a create that failed and was rolled back. A machine's
history is GET /api/v1/tasks?object=vm-3f9a1c2e; see Tasks.
GET /api/v1/resources lists every machine with its state in one answer.
Starting, stopping and restarting are on Lifecycle and
restarts. Errors come back as {"error": "…"} in plain
words: 400 for a request that is not valid, 404 for an unknown machine or
image, 409 for a conflict (a name in use, a state that does not allow it, a
quota reached), 503 while Compute is not ready, 403 when the licence does
not allow it. Unknown fields in the body are refused, not ignored.
Tenant-scoped API keys cannot manage machines yet; use an operator token or key.