Skip to content

Virtual Media Guide

This guide explains how to use Redfish virtual media to attach ISO images to KubeVirt virtual machines.

Overview

Virtual media allows attaching ISO images to VMs over the network, enabling:

  • Operating system installation from ISO
  • Booting from recovery media
  • Running live systems

KubeVirtBMC implements Redfish virtual media using KubeVirt's DeclarativeHotplugVolumes feature and CDI (Containerized Data Importer).

Prerequisites

1. CDI Installation

CDI (Containerized Data Importer) must be installed:

# Check if CDI is installed
kubectl get crd datavolumes.cdi.kubevirt.io

# If not installed, see: https://github.com/kubevirt/containerized-data-importer

2. KubeVirt Feature Gate

The DeclarativeHotplugVolumes feature gate must be enabled:

# Check KubeVirt configuration
kubectl get kubevirt kubevirt -n kubevirt -o yaml | grep DeclarativeHotplugVolumes

If the feature gate is not enabled, enable it using the patch command:

kubectl patch kubevirt kubevirt -n kubevirt --type merge -p '{"spec":{"configuration":{"developerConfiguration":{"featureGates":["DeclarativeHotplugVolumes"]}}}}'

Ensure it's enabled in KubeVirt spec:

spec:
  configuration:
    developerConfiguration:
      featureGates:
      - DeclarativeHotplugVolumes

3. VirtualMachine Configuration

Your VirtualMachine must have a CD-ROM disk defined:

kubectl apply -f - <<EOF
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: testvm
  namespace: default
spec:
  runStrategy: Always
  template:
    spec:
      domain:
        devices:
          disks:
          - name: containerdisk
            disk:
              bus: virtio
          # CD-ROM disk required for virtual media
          - cdrom:
              bus: sata
            name: cdrom
          interfaces:
          - name: default
            masquerade: {}
        resources:
          requests:
            memory: 64Mi
      volumes:
      - name: containerdisk
        containerDisk:
          image: quay.io/kubevirt/cirros-container-disk-demo
      networks:
      - name: default
        pod: {}
EOF

Important:

  • CD-ROM disk must exist in spec before using virtual media
  • Bus type should be sata (recommended) or scsi
  • Disk name can be any value (e.g., cdrom, iso, dvd)

Selecting a StorageClass

By default, the DataVolume created on virtual media insert uses your cluster's default StorageClass. If your cluster has no default StorageClass, or you want virtual media images to land on a specific StorageClass, set spec.redfish.virtualMedia.storage.storageClassName on the VirtualMachineBMC resource:

spec:
  redfish:
    virtualMedia:
      storage:
        storageClassName: my-storage-class

Breaking API Change

Starting with v0.11.0, storageClassName has been moved from spec.storageClassName to spec.redfish.virtualMedia.storage.storageClassName. The old location at spec.storageClassName is no longer supported. If you are upgrading from a previous version, update your VirtualMachineBMC resources accordingly.

Selecting a Volume Mode

By default, the DataVolume created on virtual media insert uses Block volume mode, which has no filesystem overhead and so isn't subject to the StorageClass's CDI filesystemOverhead setting described below. This requires a StorageClass whose provisioner supports raw block volumes. If yours doesn't, or you need a filesystem-backed volume for another reason, set spec.redfish.virtualMedia.storage.volumeMode to Filesystem on the VirtualMachineBMC resource:

spec:
  redfish:
    virtualMedia:
      storage:
        volumeMode: Filesystem

Storage Overhead

If you are using a storage backend with higher filesystem overhead than CDI's default assumption of 6% (e.g. Ceph RBD which can have ~9.5% overhead), you may still encounter the following error:

virtual image size is larger than the reported available storage. A larger PVC is required

In this case, you need to increase the filesystemOverhead in your CDI config to match your storage backend's actual overhead. Set the value to a percentage slightly above your backend's real overhead (e.g. 0.15 for 15%, which gives a safe buffer for Ceph RBD):

kubectl patch cdi cdi --type=merge -p \
  '{"spec":{"config":{"filesystemOverhead":{"global":"0.15"}}}}'

Note

This tells CDI to allocate a larger scratch PVC during import, ensuring there is enough usable space after the storage backend takes its overhead cut.

Verify it applied:

kubectl get cdiconfig config -o jsonpath='{.status.filesystemOverhead}'

Padding the DataVolume Size

filesystemOverhead is a cluster-wide CDI setting, and it has no effect on Block volumes at all. If you need to pad the size of individual virtual media DataVolumes instead, for example, an exact-size image that doesn't leave enough room for a Block volume's own storage backend rounding, set the bmc.kubevirt.io/datavolume-size-margin annotation on the VirtualMachineBMC resource to a percentage:

metadata:
  annotations:
    bmc.kubevirt.io/datavolume-size-margin: "30"

This pads the requested DataVolume size by the given percentage (30% in the example above). The annotation is absent by default (no padding); an invalid (non-integer) value is logged as a warning and also treated as no padding.

TLS for Private or Self-Signed HTTPS Images

By default, an HTTPS image URL must present a certificate trusted by the public CA bundle. If your image server uses a certificate signed by a private or internal CA, for example, a metal3/Ironic virtual media server, configure TLS behavior under spec.redfish.virtualMedia.tls on the VirtualMachineBMC resource. This setting applies both when virtbmc checks the remote image's size before creating the DataVolume, and when CDI imports the image.

Trusting a Private CA

Create a ConfigMap containing the CA certificate under the key ca.pem, in the same namespace as the VirtualMachineBMC:

kubectl create configmap my-image-server-ca \
    --from-file=ca.pem=./ca.pem \
    -n default

Then reference it:

spec:
  redfish:
    virtualMedia:
      tls:
        caBundleConfigMapRef:
          name: my-image-server-ca

Skipping Certificate Verification

TLS certificate verification can also be disabled entirely:

spec:
  redfish:
    virtualMedia:
      tls:
        insecureSkipVerify: true

Warning

insecureSkipVerify disables TLS certificate verification entirely, making the connection vulnerable to man-in-the-middle attacks. Prefer caBundleConfigMapRef when possible, and only use insecureSkipVerify in trusted network environments.

Inserting Virtual Media

Using Redfish API

# First, create a session and get token
TOKEN=$(curl -s -i -X POST \
    -H "Content-Type: application/json" \
    http://testvm-virtbmc.default.svc/redfish/v1/SessionService/Sessions \
    -d '{"UserName":"admin","Password":"admin123"}' \
    | grep -i "X-Auth-Token" | cut -d' ' -f2 | tr -d '\r')

# Insert virtual media
curl -i -X POST \
    -H "Content-Type: application/json" \
    -H "X-Auth-Token: $TOKEN" \
    http://testvm-virtbmc.default.svc/redfish/v1/Managers/BMC/VirtualMedia/CD1/Actions/VirtualMedia.InsertMedia \
    -d '{
        "Image": "https://releases.ubuntu.com/noble/ubuntu-24.04.3-live-server-amd64.iso",
        "Inserted": true
    }'

Ejecting before re-inserting

InsertMedia and EjectMedia are Redfish POST actions, which are non-idempotent by definition. If media is already inserted, call VirtualMedia.EjectMedia before issuing another VirtualMedia.InsertMedia request. Calling InsertMedia again while media is already attached is undefined behavior and should not be used as a retry or polling mechanism.

Booting from Virtual Media

After inserting virtual media, a DataVolume is created and the ISO image begins downloading. Once the download is complete, configure the VM to boot from the CD-ROM and restart it:

InsertMedia is asynchronous

InsertMedia returns as soon as the DataVolume is created — it does not wait for the ISO to finish downloading. KubeVirtBMC forces the import to start immediately (via the cdi.kubevirt.io/storage.bind.immediate.requested annotation), so this works even on stopped VMs and WaitForFirstConsumer storage classes.

Power-on requests are accepted immediately, but the VM's pod won't schedule until the DataVolume reaches Succeeded — so provisioning tools with power-on timeouts may time out waiting for the VM to come up. Wait for Succeeded (below) before powering on to avoid this.

# Watch the DataVolume download progress
kubectl get datavolume testvm -n default -w

# Set boot override to CD-ROM
curl -i -X PATCH \
    -H "Content-Type: application/json" \
    -H "X-Auth-Token: $TOKEN" \
    http://testvm-virtbmc.default.svc/redfish/v1/Systems/1 \
    -d '{
        "Boot": {
            "BootSourceOverrideTarget": "Cd",
            "BootSourceOverrideEnabled": "Once"
        }
    }'

# Restart the VM to boot from CD-ROM
curl -i -X POST \
    -H "Content-Type: application/json" \
    -H "X-Auth-Token: $TOKEN" \
    http://testvm-virtbmc.default.svc/redfish/v1/Systems/1/Actions/ComputerSystem.Reset \
    -d '{"ResetType":"GracefulRestart"}'

In-guest reboot handling

If the guest OS reboots itself (e.g., an installer rebooting after it finishes), KubeVirt by default restarts the guest inside the existing VMI, which still carries the CD-ROM-first boot order it was created with — so the VM boots from the virtual media again instead of from the freshly installed disk. Setting rebootPolicy: Terminate on the KubeVirt VirtualMachine makes guest reboots recreate the VMI from the VM template, which KubeVirtBMC has already restored to the default boot order when the one-shot was consumed, so the VM boots from disk as expected. See the Redfish Guide for details. This requires KubeVirt 1.8.0+ with the RebootPolicy feature gate enabled.

Request Parameters

Parameter Type Required Description
Image string Yes HTTP/HTTPS URL to ISO image
Inserted boolean Yes Set to true to insert media

Supported Image Sources

  • HTTP URLs: http://example.com/image.iso
  • HTTPS URLs: https://example.com/image.iso
  • HTTPS URLs signed by a private or self-signed CA: see TLS for Private or Self-Signed HTTPS Images
  • Public repositories: Ubuntu, CentOS, etc.
  • Internal services: Services accessible from the cluster

Example ISO URLs

# Ubuntu 24.04
"https://releases.ubuntu.com/noble/ubuntu-24.04.3-live-server-amd64.iso"

# CentOS Stream
"https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso"

# Internal service
"http://internal-iso-service.default.svc/image.iso"

Checking Status

Get Virtual Media Status

curl -H "X-Auth-Token: $TOKEN" \
    http://testvm-virtbmc.default.svc/redfish/v1/Managers/BMC/VirtualMedia/CD1 | jq

Response includes:

  • Image: The ISO URL
  • Inserted: Whether media is currently inserted
  • MediaTypes: Supported types (CD, DVD)

Ejecting Virtual Media

Eject Virtual Media

curl -i -X POST \
    -H "Content-Type: application/json" \
    -H "X-Auth-Token: $TOKEN" \
    http://testvm-virtbmc.default.svc/redfish/v1/Managers/BMC/VirtualMedia/CD1/Actions/VirtualMedia.EjectMedia \
    -d '{}'

Note: The request body must be an empty JSON object {}.

Next Steps