> ## Documentation Index
> Fetch the complete documentation index at: https://docs.celum.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a VM

> Clone a golden image into a running KubeVirt virtual machine in your project — sized, networked, and provisioned at first boot.

By the end of this page a virtual machine is running in your project's namespace, cloned from a library image, provisioned by cloud-init at first boot, and reachable on whichever network model you chose — including, if you asked for one, an address that will survive live migration.

This page starts where [Create a project](https://docs.celum.io/get-started/create-a-project.md) ended: a namespace of your own, on a supervisor whose Virtualization step is done.

> [!TIP]
>
> Is the VM already running in VMware vCenter? Move it instead of rebuilding it: [Migrate VMs from vSphere](https://docs.celum.io/vms/migrate-from-vsphere.md).

## What this changes on your cluster

A KubeVirt `VirtualMachine` in your project namespace, a PVC cloned from the library's golden image, and a Secret holding the cloud-init user data — owned by the VM, so deleting the VM reaps it.

Nothing outside the namespace changes, with one exception worth knowing: asking for an identity address claims an IP from the supervisor's VM-identity pool, which is shared estate.

```mermaid theme={null}
flowchart LR
    G[(Golden image PVC)] -->|"CSI clone"| D[(VM root disk)]
    D --> V[VirtualMachine]
    S[cloud-init Secret] --> V
    P[VM identity pool] -->|"claim stamped at create"| V
```

## Before you start

* **The Virtualization step**, from onboarding — KubeVirt and CDI.
* **A project namespace.** Leave the namespace empty and the VM lands in `default`, which is not a tenant boundary and not what you want.
* **An image in the library with `status: ready`.** A cloning image is not usable yet, and the create call refuses it by name.
* **Permissions**: `vm:Create`, scoped `supervisor:<name>:vm:*`.

> [!WARNING]
>
> If you want live migration or an identity address, the image must be **ReadWriteMany**. An RWO image cannot move between nodes, and the create call says so outright: *"identity network requires a live-migratable image (ReadWriteMany storage); image `<name>` is ReadWriteOnce"*. Celum's own end-to-end suite filters RWO images out before it picks one, for exactly this reason.

## The shape of it

**Pick an image → size it → choose the network → say what runs at first boot → create.** The network choice is the one that is expensive to change afterwards.

## Step 1 — Pick an image

Three fields on a library image decide whether it is the one you want:

| Field | Values | What it decides |
| - | - | - |
| `status` | `ready` / cloning / failed | whether it can be used at all |
| `imageType` | `disk` / `iso` | a bootable clone, or an installer that needs a blank disk |
| `accessMode` | `ReadWriteMany` / `ReadWriteOnce` | whether the VM can ever live-migrate |

> [tab] Dashboard
>
> Workloads → **New workload** → **Virtual machine**. The image picker lists the VM library.

> [tab] API
>
> `GET /api/images` — the library is platform-wide, not per supervisor. Filter on `library: "vm"` and `status: "ready"`.

> [tab] MCP
>
> `list_images`.

![The Create Virtual Machine form on supervisor prod with Name and Project, image cards marked Ready to boot (Ubuntu 26.04 cloud image, a golden image, Windows Server 2022), disk size and storage class, instance type u1.nano, network mode NAT, Bridge or passt, and the first-boot SSH public keys](https://mintcdn.com/celum-e0119be0/sA7S2Ajgv1l2CH1V/images/get-started/vm-create.png?fit=max\&auto=format\&n=sA7S2Ajgv1l2CH1V\&q=85\&s=4d70d4bea53219dccdb2f582c089ddfe)

Create Virtual Machine: image, disks, instance type, network mode and first boot on one form

**Result:** an image id. Everything after this point is a decision about how to run it.

## Step 2 — Size it

`instanceType` and `diskSize` are the two you set; `storageClass` falls back to the supervisor's default when empty.

Instance types come in families, and the family prefix tells you what it promises:

| Family | Promise |
| - | - |
| `u` | general purpose |
| `o` | overcommitted — higher density, overcommitted memory |
| `cx` | compute exclusive — dedicated CPUs, hugepages, vNUMA |
| `m` | memory-intensive, 1:8 vCPU:memory |
| `n` | network — DPDK / VNFs, dedicated CPUs, hugepages |
| `rt` | realtime — deterministic latency, dedicated CPUs |
| `d` | dedicated — guaranteed compute, isolated emulator thread |
| `gn` | GPU passthrough |

> [!NOTE]
>
> Every family promising **dedicated CPUs** — `cx`, `n`, `rt`, `d` — only schedules on nodes whose CPU manager is enabled. If a VM on one of those sits Pending with nothing else wrong, that is the first thing to check; `u` and `o` have no such requirement.

`diskSize` defaults to **20Gi** when omitted.

## Step 3 — Choose the network model

Three modes, and the difference between them is not a preference — it decides whether the VM can ever move.

| `networkMode` | The guest sees | Live migration | Identity address |
| - | - | - | - |
| `masquerade` *(default)* | a NAT'd `10.0.2.x` address | **yes** | **yes** |
| `bridge` | the pod IP directly, no NAT | **no** | experimental |
| `passt` | the pod IP, through a user-space stack | yes | experimental |

**`bridge` drops the eviction strategy**, deliberately: an eviction strategy on a VM that cannot migrate would wedge a node drain. So choosing bridge is choosing a VM that pins to its node, and the cost surfaces at maintenance time rather than at create time.

> [!WARNING]
>
> **`passt` needs a node-level prerequisite.** It requires unprivileged user namespaces, and Talos hardening ships `user.max_user_namespaces=0` — passt then dies with *"Couldn't create user namespace: EPERM"*. A machine-config sysctl is needed before this mode works on a Talos supervisor.

An **identity address** — `identityNetwork: true` plus an `identityPool` — gives the VM a routable /32 that follows it through live migration, with no LoadBalancer involved. The claim is allocated **before** the VM object is created, so a bad pool, an exhausted one, or a non-migratable image fails cleanly with nothing half-made.

Combining identity with `bridge` or `passt` is allowed but logged as experimental: the claim's NAT chain was built for the masquerade network layout and is untested against the other two.

## Step 4 — Say what runs at first boot

Cloud-init is injected as KubeVirt `cloudInitNoCloud`. You can pass `sshKeys`, a `password`, `startupCommands`, or a **`cloudInitTemplateId`** with `cloudInitVariables` — a stored template rendered and prepended to the startup commands. Render it first if you are unsure; the preview is a separate call precisely so a broken template fails before a VM exists.

`installGuestAgent` defaults to **true** on Linux, and it is worth leaving on: guest IP and OS reporting, graceful shutdown, snapshots, and the process and listening-port views all depend on it. Windows goldens get Hyper-V enlightenments and WinRM automatically.

`rootPartitions` carve extra partitions on the boot disk; `dataDisks` attach, format and mount additional blank disks.

## Step 5 — Create it

> [tab] Dashboard
>
> Workloads → **New workload** → **Virtual machine**, then **Create VM**.

> [tab] API
>
> `POST /api/supervisors/{name}/vms`. `name`, `imageId` and `instanceType` are required; everything else has a default.

> [tab] MCP
>
> `create_vm`.

> [!WARNING]
>
> **The namespace comes from the body, and defaults to `default`.** If you also pass `?namespace=` on the URL it must name the same namespace, or the call is refused — the query parameter is what the authorisation check reads, so a mismatch would mean authorising against a project you hold and creating in one you do not.

Refusals worth recognising:

| Status | Meaning |
| - | - |
| **400** `name, imageId, and instanceType are required` | the three mandatory fields |
| **400** `Image <name> is not ready (status: …)` | the clone has not finished |
| **400** `identity network requires a live-migratable image` | an RWO image with identity asked for |
| **409** | a VM of that name exists in that namespace |

**Result:** `201 {"status": "created"}`. That means the objects were applied — **not** that the VM is running. Provisioning continues in the background.

## Verify

**The VM reaches Running.** Poll it rather than assuming; the 201 was an apply.

**The identity address is stamped.** It is written at create time, but not necessarily visible on the first read — Celum's own suite waits for `identityIP` to appear before wiring a second VM to it. Read it back rather than computing it.

**The guest agent is reporting.** Guest IP, OS and process views only populate once it is up, and its absence is the usual explanation for a VM that looks half-blank in the console.

> [tab] API
>
> `GET /api/supervisors/{name}/vms` for status; the VM detail page for `identityIP`, `identityPool` and `identityStale`.

> [tab] MCP
>
> `get_vm`, `list_vms`; `get_vm_listening_ports` and `check_vm_connectivity` once the agent is up.

## If you need to undo it

Deleting the VM removes it and reaps its cloud-init Secret through the owner reference.

**There is no MCP tool for deleting a VM, and that is policy rather than an oversight**: no tool in the catalog destroys data. Deleting requires a person, in the dashboard or over the API with `vm:Delete`.

Volumes are separate and deliberate: detaching a disk is not deleting it, and the two are different permissions (`vm:Eject` covers both detach and delete-volume). Check which one you are pressing.

## What commonly goes wrong

| Symptom | Cause |
| - | - |
| The VM was created somewhere you did not expect | the namespace comes from the body and defaults to `default` |
| `400` about a live-migratable image | the image is ReadWriteOnce; identity and live migration both need RWX |
| A VM on a `cx` / `n` / `rt` / `d` instance type stays Pending | those families need dedicated CPUs, which needs the node CPU manager |
| `passt` VMs never start, with `EPERM` on a user namespace | Talos ships `user.max_user_namespaces=0`; it needs a sysctl in the machine config |
| A `bridge` VM blocks a node drain, or cannot migrate | by design — bridge drops the eviction strategy because the VM cannot move |
| Guest IP, processes and ports are all blank | the guest agent is not running. It installs by default on Linux unless you turned it off |
| `identityIP` is empty right after create | it is stamped at create time but takes a moment to surface. Poll it |

## Next

> [card] [VM lifecycle](https://docs.celum.io/vms/lifecycle.md)
>
> Start, stop, resize, live-migrate, snapshot, clone and move between projects.

> [card] [VM networking](https://docs.celum.io/vms/networking.md)
>
> Publishing a VM port through a LoadBalancer or a Gateway.

> [card] [VM storage & disks](https://docs.celum.io/vms/storage.md)
>
> Attaching and mounting volumes — and which operations destroy data.

> [card] [VM identity pools](https://docs.celum.io/networking/vm-identity-pools.md)
>
> The pools the claim addresses come from.

> [card] [Migrate VMs from vSphere](https://docs.celum.io/vms/migrate-from-vsphere.md)
>
> Bring existing VMs in from vCenter instead of building them anew.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.