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