Images and templates¶
VM templates define the OS image used when deploying a virtual machine. Templates are either container disks (cloud-init Linux) or ISO imports (typically Windows via CDI).
Platform vs tenant templates¶
| Scope | tenant_id |
Who manages | Examples |
|---|---|---|---|
| Platform | empty | Seeded at startup; read-only in UI | cirros, ubuntu-2204, windows-server-2022 |
| Tenant | tenant UUID | Bootstrap or UI/API | e.g. fedora-39 |
Tenants see both platform and their own templates. Platform templates are not copied per tenant.
Container disks¶
KubeVirt runs container disks as ephemeral root volumes. Prefer images with cloud-init for Linux.
Sources:
| Name | Image |
|---|---|
| Cirros (demo) | quay.io/kubevirt/cirros-container-disk-demo |
| Ubuntu 22.04 | quay.io/containerdisks/ubuntu:22.04 |
| Fedora demo | quay.io/kubevirt/fedora-container-disk-demo |
Register via UI¶
- Select a tenant (root: use the tenant switcher).
- Open Images & Templates (
/templates). - Register image → Container disk or ISO (PVC).
- For container disks: name, display name, image URL, optional
#cloud-configuser-data.
Platform templates show a platform badge and cannot be edited or deleted.
Register via API¶
curl -H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-ID: $TENANT_ID" \
https://iaas.example/api/v1/vm-templates
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-ID: $TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "ubuntu-2404",
"display_name": "Ubuntu 24.04",
"image": "quay.io/containerdisks/ubuntu:24.04",
"source_type": "container",
"os_type": "linux"
}' \
https://iaas.example/api/v1/vm-templates
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/vm-templates |
Platform + tenant |
| POST | /api/v1/vm-templates |
Create tenant template |
| PATCH | /api/v1/vm-templates/{id} |
Update tenant template |
| DELETE | /api/v1/vm-templates/{id} |
Delete tenant template |
ISO import (CDI)¶
ISO templates download install media into a DataVolume/PVC in the tenant namespace.
Requirements: CDI on the cluster (Helm chart can install it), HTTP(S) ISO URL, RWO StorageClass.
Flow:
- Create template with
source_type: "iso"andimage= ISO URL import_state: importing,state: Inactive- Worker creates CDI HTTP import DataVolume
- Success →
import_state: ready,state: Active - Failure →
import_state: failed
| Field | Default | Description |
|---|---|---|
iso_size_gi |
8 | DataVolume size for the ISO |
boot_disk_size_gi |
32 | Blank boot disk at VM deploy |
storage_class |
cluster default | StorageClass for DataVolumes |
The UI polls every 5s while any template is importing.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-ID: $TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "windows-server-2022",
"display_name": "Windows Server 2022 Eval",
"image": "https://go.microsoft.com/fwlink/?linkid=2195280",
"source_type": "iso",
"iso_size_gi": 8,
"boot_disk_size_gi": 32
}' \
https://iaas.example/api/v1/vm-templates
Warning
You cannot deploy from an ISO template until import_state is ready.
Allowed ISO URLs¶
CDI downloads the ISO from inside the cluster. VirtFoundry validates the URL in the API (core#95): https on port 443 only, no embedded credentials, no loopback / link-local / private / reserved targets, and the host must be on an admin allowlist.
Configure via the API config (ConfigMap or env):
security:
iso_import:
allowed_hosts:
- "iso.mylab.example.com"
- "*.blob.core.windows.net"
disable_http_import: false
VIRTFOUNDRY_ISO_ALLOWED_HOSTS="iso.mylab.example.com,*.blob.core.windows.net"
VIRTFOUNDRY_ISO_DISABLE_HTTP_IMPORT=1
With no allowed_hosts, the built-in list covers public Microsoft / Linux install media and common object-storage hosts. Setting allowed_hosts replaces that list. Details: core VM-TEMPLATES.
CDI importer egress¶
The allowlist is name-based. CDI resolves the hostname itself, so DNS rebinding (or a redirect to a private address) can still reach cluster-internal or metadata targets if the network path exists.
VirtFoundry therefore creates an Egress NetworkPolicy in each tenant namespace (where CDI importer pods run — not the chart release namespace). A single NetworkPolicy in virtfoundry-system cannot cover tenant importers.
| Name | virtfoundry-cdi-importer-egress |
| Created by | core EnsureTenantNamespace (on tenant create and on API bootstrap for existing tenants) |
| Selects | pods labeled cdi.kubevirt.io=importer |
| Allows | DNS to kube-system / k8s-app=kube-dns (UDP+TCP 53); 0.0.0.0/0 and ::/0 except private / link-local / CGNAT ranges |
Requirements: the cluster CNI must enforce NetworkPolicy (Calico, Cilium, etc.). Without enforcement the object is inert.
Private ISO mirrors¶
If your mirror lives on RFC1918 (or another denied range), either:
- Patch the policy in the tenant NS to add an
egressipBlockfor that CIDR, or - Apply a second allow policy in the same namespace (NetworkPolicies are additive for allowed traffic).
Example second policy (also under docs/examples/):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: virtfoundry-cdi-importer-private-mirror
namespace: virtfoundry-tenant-acme # each tenant NS
spec:
podSelector:
matchLabels:
cdi.kubevirt.io: importer
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 10.10.0.0/24 # your mirror subnet
Also add the mirror hostname to security.iso_import.allowed_hosts so the API accepts the URL.
Residuals¶
- DNS rebinding to a public malicious IP is still possible; the allowlist remains the app control.
- Upload / other CDI components are not selected by this policy (only
cdi.kubevirt.io=importer).
Tracked as helm-charts#49.
Deploying with templates¶
On VMs, pick a template + service offering. See Virtual machines.