diff --git a/Makefile b/Makefile
index cb045f61218c..35a062201359 100644
--- a/Makefile
+++ b/Makefile
@@ -974,6 +974,10 @@ test-e2e: $(GINKGO) generate-e2e-templates ## Run the end-to-end tests
kind-cluster: ## Create a new kind cluster designed for development with Tilt
hack/kind-install-for-capd.sh
+.PHONY: kind-cluster-kubevirt
+kind-cluster-kubevirt: ## Create a new kind cluster with KubeVirt designed for development with Tilt
+ hack/kind-install-for-capk.sh
+
.PHONY: tilt-e2e-prerequisites
tilt-e2e-prerequisites: ## Build the corresponding kindest/node images required for e2e testing and generate the e2e templates
scripts/build-kind.sh
diff --git a/docs/book/src/developer/core/tilt.md b/docs/book/src/developer/core/tilt.md
index a9d61be09580..6b3eb55bfe62 100644
--- a/docs/book/src/developer/core/tilt.md
+++ b/docs/book/src/developer/core/tilt.md
@@ -20,14 +20,73 @@ workflow that offers easy deployments and rapid iterative builds.
## Getting started
### Create a kind cluster
-A script to create a KIND cluster along with a local Docker registry and the correct mounts to run CAPD is included in the hack/ folder.
-To create a pre-configured cluster run:
+This guide offers instructions for using the following CAPI infrastructure providers for running a
+development environment without using real machines or cloud resources:
+
+- [CAPD](https://github.com/kubernetes-sigs/cluster-api/blob/main/test/infrastructure/docker/README.md) - uses Docker containers as workload cluster nodes
+- [CAPK](https://github.com/kubernetes-sigs/cluster-api-provider-kubevirt) - uses KubeVirt VMs as workload cluster nodes
+
+CAPD is the default as it's more lightweight and requires less setup. KubeVirt is useful when
+Docker isn't suitable for whatever reason. Other infrastructure providers may be enabled as well
+(see [below](#create-a-tilt-settings-file)).
+
+{{#tabs name:"tab-management-cluster-creation" tabs:"Docker,KubeVirt"}}
+{{#tab Docker}}
+
+To create a kind cluster along with a local Docker registry and the correct mounts to run CAPD, run
+the following:
+
+```bash
+make kind-cluster
+```
+
+{{#/tab }}
+{{#tab KubeVirt}}
+
+To create a kind cluster with CAPK, run the following:
+
+```bash
+make kind-cluster-kubevirt
+```
+
+
+
+
+
+{{#/tab }}
+{{#/tabs }}
+
You can see the status of the cluster with:
```bash
@@ -36,7 +95,12 @@ kubectl cluster-info --context kind-capi-test
### Create a tilt-settings file
-Next, create a `tilt-settings.yaml` file and place it in your local copy of `cluster-api`. Here is an example that uses the components from the CAPI repo:
+Next, create a `tilt-settings.yaml` file and place it in your local copy of `cluster-api`.
+
+Here are some examples:
+
+{{#tabs name:"tab-tilt-settings" tabs:"Docker,KubeVirt"}}
+{{#tab Docker}}
```yaml
default_registry: gcr.io/your-project-name-here
@@ -46,7 +110,33 @@ enable_providers:
- kubeadm-control-plane
```
-To use tilt to launch a provider with its own repo, using Cluster API Provider AWS here, `tilt-settings.yaml` should look like:
+{{#/tab }}
+{{#tab KubeVirt}}
+
+```yaml
+enable_providers:
+- kubevirt
+- kubeadm-bootstrap
+- kubeadm-control-plane
+provider_repos:
+# Path to a local clone of CAPK (replace with actual path)
+- ../cluster-api-provider-kubevirt
+kustomize_substitutions:
+ # CAPK needs access to the containerd socket (replace with actual path)
+ CRI_PATH: "/var/run/containerd/containerd.sock"
+ KUBERNETES_VERSION: "v1.30.1"
+ # An example - replace with an appropriate container disk image for the desired k8s version
+ NODE_VM_IMAGE_TEMPLATE: "quay.io/capk/ubuntu-2204-container-disk:v1.30.1"
+# Allow deploying CAPK workload clusters from the Tilt UI (optional)
+template_dirs:
+ kubevirt:
+ - ../cluster-api-provider-kubevirt/templates
+```
+
+{{#/tab }}
+{{#/tabs }}
+
+Other infrastructure providers may be added to the cluster using local clones and a configuration similar to the following:
```yaml
default_registry: gcr.io/your-project-name-here
diff --git a/hack/kind-install-for-capd.sh b/hack/kind-install-for-capd.sh
index a0d3474c0b4b..9b677e07bcf9 100755
--- a/hack/kind-install-for-capd.sh
+++ b/hack/kind-install-for-capd.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-# Copyright 2021 The Kubernetes Authors.
+# Copyright 2025 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -30,33 +30,10 @@ if [[ "${TRACE-0}" == "1" ]]; then
set -o xtrace
fi
-KIND_CLUSTER_NAME=${CAPI_KIND_CLUSTER_NAME:-"capi-test"}
# See: https://kind.sigs.k8s.io/docs/user/configuration/#ip-family
-KIND_NETWORK_IPFAMILY=${KIND_NETWORK_IPFAMILY:-"dual"}
+KIND_NETWORK_IPFAMILY=${CAPI_KIND_NETWORK_IPFAMILY:-"dual"}
-# 1. If kind cluster already exists exit.
-if [[ "$(kind get clusters)" =~ .*"${KIND_CLUSTER_NAME}".* ]]; then
- echo "kind cluster already exists, moving on"
- exit 0
-fi
-
-# 2. Create registry container unless it already exists
-reg_name='kind-registry'
-reg_port='5000'
-if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
- docker run \
- -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \
- registry:2
-fi
-
-# 3. Create kind cluster with containerd registry config dir enabled.
-# TODO(killianmuldoon): kind will eventually enable this by default and this patch will be unnecessary.
-#
-# See:
-# https://github.com/kubernetes-sigs/kind/issues/2875
-# https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration
-# See: https://github.com/containerd/containerd/blob/main/docs/hosts.md
-cat </dev/null || true)" != 'true' ]; then
+ docker run \
+ -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \
+ registry:2
+fi
+
+# 3. Create kind cluster with containerd registry config dir enabled.
+# TODO(killianmuldoon): kind will eventually enable this by default and this patch will be unnecessary.
+#
+# See:
+# https://github.com/kubernetes-sigs/kind/issues/2875
+# https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration
+# See: https://github.com/containerd/containerd/blob/main/docs/hosts.md
+
+echo "$KIND_CLUSTER_CONFIG" | kind create cluster --name="$KIND_CLUSTER_NAME" --config=-
+
+# 4. Add the registry config to the nodes
+#
+# This is necessary because localhost resolves to loopback addresses that are
+# network-namespace local.
+# In other words: localhost in the container is not localhost on the host.
+#
+# We want a consistent name that works from both ends, so we tell containerd to
+# alias localhost:${reg_port} to the registry container when pulling images
+REGISTRY_DIR="/etc/containerd/certs.d/localhost:${reg_port}"
+for node in $(kind get nodes --name "$KIND_CLUSTER_NAME"); do
+ docker exec "${node}" mkdir -p "${REGISTRY_DIR}"
+ cat <