Skip to content

Vanilla (Kind)

Kind (Kubernetes in Docker) runs upstream Kubernetes clusters using Docker containers as nodes. It is fast (30–60 s), needs only Docker, and uses standard kind.yaml files with no vendor lock-in. On the Hetzner provider, Vanilla clusters run upstream Kubernetes with kubeadm on Hetzner Cloud servers instead of Kind. For common workflows and use cases, see Use Cases.

Requires Docker (installation prerequisites).

Terminal window
ksail project init --name my-cluster --distribution Vanilla --control-planes 1 --workers 2
ksail cluster create
ksail cluster info && kubectl get nodes # verify
ksail cluster delete # cleanup

ksail project init creates ksail.yaml (KSail config) and kind.yaml (Kind cluster config).

The ksail.yaml file controls your cluster:

# yaml-language-server: $schema=https://raw.githubusercontent.com/devantler-tech/ksail/main/schemas/ksail-config.schema.json
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
distribution: Vanilla
distributionConfig: kind.yaml

Customize Kind behavior in kind.yaml:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
# Port mapping for accessing services
extraPortMappings:
- containerPort: 80
hostPort: 8080
protocol: TCP
- role: worker
- role: worker

Enable LoadBalancer services with Cloud Provider KIND:

ksail.yaml
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
distribution: Vanilla
loadBalancer: Enabled

When enabled, KSail installs Cloud Provider KIND, which creates cpk-* Docker containers as LoadBalancer proxies and assigns external IPs. See LoadBalancer Configuration for details.

KSail enables docker.io, ghcr.io, quay.io, and registry.k8s.io mirrors by default. The --mirror-registry flag uses replace semantics: any values you provide completely replace the full default list, so omitting a registry removes it. To customize mirrors while keeping the defaults, include all four:

Terminal window
ksail project init --name my-cluster --distribution Vanilla \
--mirror-registry 'docker.io=https://registry-1.docker.io' \
--mirror-registry 'ghcr.io=https://ghcr.io' \
--mirror-registry 'quay.io=https://quay.io' \
--mirror-registry 'registry.k8s.io=https://registry.k8s.io'

Symptom: ksail cluster create fails with Docker errors.

Terminal window
docker ps # Verify Docker is running
df -h # Check disk space
docker system prune -f
ksail cluster create

Symptom: kubectl get nodes shows NotReady status.

Terminal window
kubectl describe node <node-name>
kubectl get pods -n kube-system | grep cilium
kubectl rollout restart daemonset/cilium -n kube-system

Symptom: LoadBalancer service never gets EXTERNAL-IP. Ensure loadBalancer: Enabled is set in ksail.yaml, then run ksail cluster update. See LoadBalancer Configuration.

Symptom: address already in use error during cluster creation. List and delete conflicting Kind clusters:

Terminal window
kind get clusters
kind delete cluster --name <conflicting-cluster>

Symptom: Cluster creation fails with resource errors.

Terminal window
ksail project init --name my-cluster --distribution Vanilla --control-planes 1 --workers 0
# WARNING: removes ALL unused images and volumes — can affect other Docker projects
docker system prune -a --volumes

Create production-like topologies:

Terminal window
ksail project init --name multi-node --distribution Vanilla --control-planes 3 --workers 5

Set the node image in kind.yaml to pin a Kubernetes version. Check Kind release notes for available images. Edit the nodes: section of the generated kind.yaml:

# kind.yaml (partial — nodes: section)
nodes:
- role: control-plane
image: kindest/node:v1.35.1
- role: worker
image: kindest/node:v1.35.1

Override the default CNI:

Terminal window
ksail project init --name my-cluster --distribution Vanilla --cni Calico

Expose services on host ports. Edit the nodes: section of the generated kind.yaml:

# kind.yaml (partial — nodes: section)
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080 # NodePort service
hostPort: 8080
protocol: TCP

Access services at http://localhost:8080.

Enable persistent storage:

Terminal window
ksail project init --name my-cluster --distribution Vanilla --csi Enabled

Enable containerd image signature verification via the io.containerd.image-verifier.v1.bindir plugin:

Terminal window
ksail project init --name my-cluster --distribution Vanilla --image-verification Enabled

This injects a containerdConfigPatches entry into kind.yaml that activates the plugin at bin_dir = "/opt/image-verifier/bin" on every node. Verification only runs when verifier binaries (e.g., Cosign, Notation) are pre-installed in a custom Kind node image at that path — if the directory is absent or empty, image pulls proceed without verification.

Requirements:

  • containerd 2.x (Kind v0.31.0+ / kindest/node:v1.35.1+)
  • Custom Kind node image with verifier binaries pre-installed at /opt/image-verifier/bin

The kind.yaml generated by ksail project init is standard Kind config — use it directly without KSail:

Terminal window
kind create cluster --config kind.yaml
kind export kubeconfig