Skip to content

Installation

VirtFoundry installs the control plane (API, UI). Platform state is stored in virtfoundry.io CRDs. Virtual machines and networking rely on KubeVirt, Multus, and CDI on the cluster.

Install prerequisites first

See Platform prerequisites for official install links to KubeVirt, Multus, CDI, storage, and optional MetalLB / CSI snapshots.

Recommended order: prerequisites → virtfoundry-operator (CRDs + controller) → virtfoundry (API + UI).

Want UI + first VM in under 30 minutes? Start with the Quickstart. On a laptop (Docker, no switch/VLAN), use Kind.

For minimum vs production layouts (what works for VPC / public / snapshots on a home router), see Deployment topologies.

Prerequisites overview

Component Required? Role in VirtFoundry
Kubernetes 1.28+ Yes Runs all workloads
Helm 3.x Yes Installs the charts
virtfoundry-operator Yes (CRD store) Installs virtfoundry.io CRDs and reconciles Tenant/Instance status
KubeVirt Yes Hypervisor — VMs, start/stop, console, VM snapshots
Multus CNI Yes Secondary NICs — tenant VPCs, isolated L2, public VM network
CDI Yes for ISO/import templates; optional for container-disk-only Imports ISOs and blank boot disks via DataVolume
Ingress or Gateway API + controller One of them Exposes UI and API on a hostname
StorageClass — prefer Longhorn Yes for disks PVCs for VM volumes, ISO storage; local-path only for quick labs
CSI snapshotter + snapshot-capable CSI (Longhorn includes this) Recommended; required for volume snapshots UI VolumeSnapshot CRDs + VolumeSnapshotClass; not provided by local-path
MetalLB (or cloud LB) Bare metal only When Services need external IPs

Not bundled in the Helm chart by default

KubeVirt, Multus, and CDI are cluster-scoped platform operators. They are installed separately so you can pin versions, align with your distro, and upgrade them independently of VirtFoundry releases.


Why each platform component is needed

KubeVirt — required

VirtFoundry does not embed a hypervisor. The API talks to KubeVirt CRDs (and the operator syncs Instance status back to virtfoundry.io CRs):

  • VirtualMachine / VirtualMachineInstance — create, start, stop, delete VMs
  • VirtualMachineSnapshot — VM snapshots (point-in-time of the guest; not the same as CSI volume snapshots)
  • VNC subresource — web console in the UI

Without KubeVirt, deploy and lifecycle operations fail immediately (kubevirt.enabled assumes the KubeVirt API is reachable).

Volume snapshots ≠ VM snapshots

The Volume Snapshots page creates Kubernetes VolumeSnapshot objects (snapshot.storage.k8s.io). That API is not installed by KubeVirt and is not available with only local-path. Without CSI external-snapshotter + a snapshot-capable StorageClass (Longhorn, Ceph RBD, cloud CSI, …), the UI returns the server could not find the requested resource. Use VM Snapshots on lab/local-path clusters, or install a CSI snapshot stack for volume snapshots. Details: Configuration — Snapshots.

Verify:

kubectl get pods -n kubevirt
kubectl get crd virtualmachines.kubevirt.io

Multus CNI — required

VirtFoundry models multi-tenant networking: tenants, VPCs, security groups, and an optional shared public network. That requires more than the default pod CNI:

Feature How VirtFoundry uses Multus
Tenant VPC / private networks Creates NetworkAttachmentDefinition (NAD) per network on an isolated bridge
Public / routable VM IPs Secondary NIC on a bridge or macvlan NAD + cloud-init addressing
Security groups Kubernetes NetworkPolicy on the pod network; extra NICs use Multus interfaces

The hypervisor driver attaches Multus networks to VM launcher pods (v1.multus-cni.io/default-network and additional NADs). VPCs, custom networks, and public IP pools do not work without Multus.

Pod-only VMs (no network_ids, public network disabled) still use KubeVirt’s masquerade interface, but the product expects Multus for full IaaS functionality.

Verify:

kubectl get pods -n kube-system -l app=multus
kubectl get crd network-attachment-definitions.k8s.cni.cncf.io

CDI — required for ISO and import workflows

CDI provides DataVolume resources. VirtFoundry uses CDI when:

  • Registering an ISO template (HTTP import of an .iso into a PVC)
  • Creating a blank boot disk for install-from-ISO (e.g. Windows eval)
  • Waiting for import completion before a VM can boot from ISO

Container-disk templates (image URL pointing at a registry-hosted disk image) can work without CDI — KubeVirt pulls the image directly as a containerDisk.

Template / deploy path CDI needed?
Linux cloud image (container disk) No
ISO template or install-from-ISO Yes
Attaching an existing imported volume Yes (volume created via CDI)

If you only use container-disk templates, you can skip CDI initially; enable it before using ISO features.

Verify:

kubectl get pods -n cdi
kubectl get crd datavolumes.cdi.kubevirt.io

Installing platform components

The chart can optionally trigger install hooks (platform.multus.install, platform.cdi.install, KubeVirt job). By default these are off — most clusters install platform software once, outside VirtFoundry upgrades.

Recommended: use the helper scripts (idempotent) from a chart clone:

export KUBECONFIG=/path/to/kubeconfig

./scripts/setup/kubevirt.sh   # KubeVirt operator + CRDs
./scripts/setup/multus.sh     # Multus DaemonSet
./scripts/setup/cdi.sh        # CDI operator

Or install from upstream docs and verify CRDs before proceeding.

Order: Multus and storage class first → KubeVirt → CDI (CDI depends on KubeVirt CRDs).


Install VirtFoundry from Helm repository

After platform prerequisites are healthy:

helm repo add virtfoundry https://virtfoundry.github.io/helm-charts
helm repo update

# 1. CRDs + operator (required)
helm install virtfoundry-operator virtfoundry/virtfoundry-operator \
  --namespace virtfoundry-system \
  --create-namespace

# 2. API + UI
helm install virtfoundry virtfoundry/virtfoundry \
  --namespace virtfoundry-system \
  --set secrets.rootPassword='choose-a-strong-password' \
  --set secrets.jwtSecret="$(openssl rand -hex 32)"

The chart ships no credential defaults — see Secrets for the rules and for the secrets.existingSecret path.

Pin a release (same CRD store flags):

helm install virtfoundry-operator virtfoundry/virtfoundry-operator \
  --version 0.7.3 \
  --namespace virtfoundry-system \
  --create-namespace

helm install virtfoundry virtfoundry/virtfoundry --version 0.7.3 \
  --namespace virtfoundry-system \
  --set secrets.rootPassword='choose-a-strong-password' \
  --set secrets.jwtSecret="$(openssl rand -hex 32)"

Images default to ghcr.io/virtfoundry/core:0.7.3, ui:0.7.3, and operator:0.7.3.


Why --set is on the Helm command

--set is a Helm flag. It must appear on the same helm install / helm upgrade line as the chart. It does not work as a follow-up kubectl command.

  • Must set at install: secrets.rootPassword, secrets.jwtSecret — or secrets.existingSecret. The chart has no defaults for them and fails to render without one of the two (Secrets).
  • Usually omit: public IP CIDR — see below. Storage class — auto picks Longhorn when it exists.
  • Prefer -f: anything more than two keys. Written defaults: Chart values.

VirtFoundry never ships disks. platform.storage.defaultClass: auto selects longhorn if that StorageClass is already on the cluster, otherwise the cluster default, otherwise local-path.

Install Longhorn first if you want replicated VM disks and volume snapshots. local-path is fine for a first VM; volume snapshots need CSI (Longhorn provides that). Pin the class with --set platform.storage.defaultClass=longhorn if auto is not enough.


Public IP — optional; homelab can inherit the node LAN

Leave public unset (enabled: false, chart default) to install the UI. VMs stay on the pod network.

If you enable public later, CIDR/gateway/pool default to the first Node InternalIP (autoFromCluster: true) so a single-LAN homelab does not invent 10.0.50.0/24. That is the Kubernetes address, not a VLAN. Dedicated VM VLANs (this project's homelab uses 10.0.50.0/24 on enp3s0.50) must set CIDR by hand and autoFromCluster: false. Auto never sets uplink (that would steal the kubelet NIC).

Script: scripts/detect-host-public-net.sh. Full values: Chart values. Underlay choices: Topologies.


Install from git clone

git clone https://github.com/virtfoundry/helm-charts.git
cd helm-charts

helm install virtfoundry-operator ./charts/virtfoundry-operator \
  --namespace virtfoundry-system --create-namespace

helm install virtfoundry ./charts/virtfoundry \
  --namespace virtfoundry-system \
  --set secrets.rootPassword='choose-a-strong-password' \
  --set secrets.jwtSecret="$(openssl rand -hex 32)"

Validate templates:

make lint

First login

Bootstrap credentials come from the chart — there is no built-in default password:

  • User: root
  • Password: the secrets.rootPassword you passed at install (or ROOT_PASSWORD in your secrets.existingSecret)

API base path: /api/v1 on the same hostname as the UI.

Verify CRD store

After install:

kubectl get crd | grep virtfoundry.io
kubectl get vf-tenant
kubectl get vf-instance -A
kubectl get vmsnapshot -A
kubectl get pods -n virtfoundry-system

You should see virtfoundry-operator and virtfoundry-api Running, Instance CRs for your VMs, and KubeVirt VirtualMachineSnapshot objects when you use VM Snapshots in the UI.


Next steps