KubeVirt on K3s Playground
Multi-node K3s cluster with KubeVirt, CDI and the KubeVirt Manager web UI preinstalled. Run virtual machines next to containers.
- eth0
- /
- oci://ghcr.io/omkar-shelke25/kubevirt-playground:dev-machine
- eth0
- /
- oci://ghcr.io/omkar-shelke25/kubevirt-playground:cplane
- eth0
- /
- oci://ghcr.io/omkar-shelke25/kubevirt-playground:node
- eth0
- /
- oci://ghcr.io/omkar-shelke25/kubevirt-playground:node
A multi-node K3s cluster with KubeVirt preinstalled, ready to run virtual machines next to containers.
Machines
| Machine | IP | Role |
|---|---|---|
dev-machine | 172.16.0.5 | Workstation with kubectl, virtctl, helm, k9s, Docker and vm-vnc |
cplane-01 | 172.16.0.2 | K3s control plane, KubeVirt Manager on port 30080 |
node-01 | 172.16.0.3 | K3s worker, runs VMs |
node-02 | 172.16.0.4 | K3s worker, runs VMs |
Included
K3s, KubeVirt, CDI, KubeVirt Manager, virtctl, kubectl, helm, k9s, Docker, noVNC, and a Tokyo Night zsh terminal on every machine.
Components
| Component | Namespace | What it does |
|---|---|---|
virt-operator | kubevirt | Installs and manages the other KubeVirt components |
virt-api | kubevirt | API entry point for VM actions, serial console and VNC |
virt-controller | kubevirt | Creates a virt-launcher pod for every VM that starts |
virt-handler | kubevirt | DaemonSet, one pod per node, runs and monitors the VMs on that node |
virt-launcher-<vm> | VM's namespace | One pod per running VM, runs QEMU |
cdi-operator | cdi | Installs and manages the other CDI components |
cdi-apiserver | cdi | API for DataVolumes |
cdi-deployment | cdi | Imports disk images into PVCs |
cdi-uploadproxy | cdi | Receives local images from virtctl image-upload |
kubevirt-manager | kubevirt-manager | Web UI behind the KubeVirt Manager tab |
Tabs
| Tab | Opens |
|---|---|
| IDE | VS Code on dev-machine |
| Explorer | Kubernetes resource explorer |
| KubeVirt Manager | Web UI to create, start, stop and inspect VMs |
| VM Screen | A VM's display in the browser (run vm-vnc first) |
Wait for the cluster
The playground opens once all three K3s nodes are Ready, but KubeVirt, CDI and KubeVirt Manager keep starting in the background for a few more minutes. Run these on dev-machine before creating any VM. Each command returns as soon as its component is ready.
Nodes:
kubectl get nodes
All three nodes must show Ready.
KubeVirt:
kubectl -n kubevirt wait kv kubevirt --for condition=Available --timeout=10m
CDI:
kubectl -n cdi wait cdi cdi --for condition=Available --timeout=10m
KubeVirt Manager:
kubectl -n kubevirt-manager rollout status deployment kubevirt-manager --timeout=10m
To see every component pod at once:
kubectl get pods -n kubevirt -o wide
kubectl get pods -n cdi
All pods must be Running with every container ready (for example 1/1).
Quick start
From dev-machine, create and start a small test VM:
kubectl apply -f https://kubevirt.io/labs/manifests/vm.yaml
virtctl start testvm
kubectl get vmi testvm -w
Wait for PHASE Running and READY True, then press Ctrl+C.
Serial console (exit with Ctrl+]):
virtctl console testvm
VM screen in the browser (VNC)
One command on dev-machine, with the VM running:
vm-vnc testvm
Then open the VM Screen tab. Press Ctrl+C in the terminal to stop.
For a VM in another namespace:
vm-vnc testvm my-namespace
vm-vnc does everything for you. It starts the KubeVirt VNC proxy and the browser bridge, and restarts the proxy after every session so reloading the tab reconnects. You never run virtctl vnc or websockify by hand.
vm-vnc stays in the foreground. Leave it running in one terminal tab and use another dev-machine terminal for other work.
Rules
- A VM accepts only one VNC session at a time. Do not run
virtctl vncby hand whilevm-vncis running, or the two keep disconnecting each other. - Run one
vm-vncat a time. To switch VMs, pressCtrl+Cand runvm-vncwith the other VM name. - Cloud images such as CirrOS and AlmaLinux show a text login prompt, not a desktop. If the screen is black, click inside it and press
Enter.
Troubleshooting
| Symptom | Fix |
|---|---|
kubectl wait times out | Check the pods with kubectl get pods -n kubevirt and kubectl get pods -n cdi, then kubectl describe pod <name> -n <namespace> for the one that is not ready |
VM default/testvm is not running | Run virtctl start testvm and wait for READY True |
| VM Screen tab shows an error page | vm-vnc is not running. Run it on dev-machine |
| Screen connects, then drops right away | Another VNC session holds the VM. Run pkill -f 'virtctl vnc', then vm-vnc again |
Address already in use on port 6080 | An old bridge is still running. Run pkill websockify, then vm-vnc again |
| Black screen | Click inside the screen and press Enter |
Limitations
iximiuz Labs playgrounds have no nested virtualization, so KubeVirt runs VMs with QEMU software emulation. Small images such as CirrOS boot in seconds, full distros take a few minutes.
containerDisk volumes are ephemeral. Anything written inside the VM is lost on every VM restart. Use a DataVolume on the local-path storage class for persistence.
A real VM, not a container
Get root on a VM with its own kernel, so Docker, Kubernetes, and systemd just work.
Read the docs →
SSH from browser or CLI
Use the built-in web terminal, or connect with labctl ssh, plain ssh, scp, or rsync.
Read the docs →
Drive it with AI
Let Claude, Codex, or any MCP client start this playground and run commands in it.
Read the docs →
Expose HTTP(S) ports
Give any web app running inside the VM a public URL - for yourself or to share with others.
Read the docs →
Share terminals
Invite others into your terminal session, or open it yourself from another device.
Read the docs →
Customize with init scripts
Add shell scripts that run at boot to install packages, clone repos, or seed test data.
Read the docs →
Private networking
VMs reach the internet via NAT with no public IP; several VMs share bridge networks.
Read the docs →
Ephemeral or persistent
By default, VMs are destroyed when the session ends; enable persistence to keep the disks for next time.
Read the docs →
Built-in container registry
A private registry.iximiuz.com for every playground to share images across services and VMs.
Read the docs →