A KubeVirt VM is one of the heaviest stateful workloads you can put on Syself Autopilot. Nodes are ephemeral: they are drained and rebuilt on upgrades, rollouts, and repairs. A container restarts on another node in seconds, but a virtual machine holding 8Gi of memory and a disk on local NVMe does not move so cleanly, and it can lose its in-memory state and local disk when its node goes away. So before you reach for the manifest, understand what happens the first time that node is drained. ## What happens to your VM when the node drains When a bare-metal node is drained, the VM’s pod (the virt-launcher) is evicted like any other, and KubeVirt reschedules the VM onto another node, but as a fresh VM from its disk image. If that disk sat on the node’s [local NVMe](/docs/hetzner/apalla/storage/local/local-nvme-with-topolvm), its contents stayed on the old node and are gone. Take backups of all data on local NVMe disks and keep anything you cannot lose on network-backed storage or outside the VM. A heavy VM shuts down cleanly only if the drain waits long enough. How long the drain waits before the node is torn down is set per pool by `spec.topology.workers.machineDeployments[].deletion.nodeDrainTimeoutSeconds`. Set it high enough on the pool running your VMs that a large VM can flush its state and power off instead of being cut off mid-write. A pool is one `machineDeployments` entry in your `Cluster` resource; see [Node pools and machine deployments](/docs/hetzner/apalla/servers-and-nodes/pools/node-pools-overview) for where to set this field. ## It runs on bare metal only KubeVirt needs hardware virtualization (KVM). Hetzner bare-metal servers have it; Hetzner Cloud instances lack nested virtualization, so VMs will not run there. If your cluster has no bare-metal nodes, [add a bare-metal pool](/docs/hetzner/apalla/servers-and-nodes/bare-metal/add-bare-metal-servers) first. It is the same KVM requirement as the [secure runtime](/docs/hetzner/apalla/workloads/runtime-classes/container-runtimes), and the two pair cleanly: KubeVirt for full virtual machines, the secure runtime for pod-level VM isolation. ## Install and define a VM KubeVirt is user-managed upstream software running in your cluster, not a platform component. Pin a specific release rather than installing from `releases/latest/download`, and design around the fact that node churn will reschedule your VMs. Install KubeVirt and CDI (the Containerized Data Importer for disk images), pinning a version: ```console $ VERSION=v1.4.0 $ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/$VERSION/kubevirt-operator.yaml $ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/$VERSION/kubevirt-cr.yaml ``` Then declare a `VirtualMachine`: ```yaml title="virtualmachine.yaml" apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: builder spec: running: true template: spec: domain: cpu: {cores: 4} resources: requests: {memory: 8Gi} devices: disks: - name: root disk: {bus: virtio} volumes: - name: root dataVolume: {name: builder-root} ``` ## Where it fits and what to reserve A VM is worth its overhead when a container cannot provide the required isolation or environment. Common cases include isolated CI/CD runners, where each build runs in a clean machine sealed off from the host and the next build; legacy applications that require a full operating system or a specific kernel and cannot be containerized; and multi-tenant platforms, where each tenant receives a strongly isolated VM. A running VM reserves its CPU and memory like a Guaranteed pod, so size the bare-metal pool for the VMs you intend to run plus headroom; otherwise a VM and your containers end up competing for the same cores. For latency-sensitive VMs on a multi-socket server, the same [NUMA alignment](/docs/hetzner/apalla/workloads/specialized/run-numa-aligned-workloads) rules apply.