# kind: Local Kubernetes in Docker

LLMS index: [llms.txt](/en/llms.txt)

---

kind creates a Kubernetes cluster from Docker containers: control-plane and worker nodes are `kindest/node` images. Primary use cases are local development and CI. GitHub Actions has an official `create-kind` action that makes pipelines with K8s tests straightforward.

Compared to minikube, kind has no hypervisor dependency and natively supports multi-node topologies. Compared to k3d, it requires no Rancher and talks to CRI/containerd directly.

## Installation

macOS, Linux, and Windows (via WSL2) — download the binary from GitHub Releases.

```bash
# macOS
brew install kind

# Linux
curl -Lo /usr/local/bin/kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64
chmod +x /usr/local/bin/kind

# Verify
kind version
# kind v0.22.0 go1.21.8 linux/amd64
```

Docker is required (or Podman with `kind use docker driver`). Make sure Docker has at least 4 GB of memory allocated for all containers.

## First Cluster in One Command

```bash
kind create cluster
# Creating cluster "kind" ...
# ✓ Ensuring node image (kindest/node:v1.29.0) ✓
# ✓ Preparing nodes ✓
# ✓ Writing configuration ✓
# ✓ Starting control-plane ✓
# ✓ Installing CNI ✓
# ✓ Installing StorageClass ✓
# ✓ Waiting for node readiness ✓
# Successfully created cluster "kind"!
```

kind created a cluster named `kind` and wrote kubeconfig to `~/.kube/config`. Verify:

```bash
kubectl get nodes
# NAME                 STATUS   ROLES           AGE   VERSION
# kind-control-plane   Ready    control-plane   2m    v1.29.0

kubectl get pods -A
# NAMESPACE            NAME                                         READY
# kube-system          coredns-...                                  1/1
# local-path-storage   local-path-provisioner-...                   1/1
```

Done. Single-node cluster is up in a minute.

## Config: Multi-node and Runtime

For a cluster with multiple worker nodes, use a YAML config. Let's create three workers:

```yaml
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 80
    hostPort: 8080
    protocol: TCP
- role: worker
- role: worker
- role: worker
```

```bash
kind create cluster --name multi --config kind-config.yaml
```

Cluster creation flags:

| Flag | Purpose | Example |
|------|---------|---------|
| `--name` | Cluster name | `kind create cluster --name prod` |
| `--config` | Path to YAML | `--config ./kind.yaml` |
| `--image` | Custom node image | `--image kindest/node:v1.28.0` |
| `--wait` | Readiness timeout | `--wait 5m` |
| `--kubeconfig` | Alternative kubeconfig | `--kubeconfig ~/.kube/dev` |

> [!TIP]
> Instead of `--image`, you can set `node.internalImage` in the config — useful for air-gapped environments.

## Loading Images into the Cluster

kind uses a separate container runtime inside the node. Images from your local Docker daemon are not visible. To load them:

```bash
# Build the image
docker build -t myapp:v1.0 ./myapp

# Load into kind nodes
kind load docker-image myapp:v1.0 --name multi

# For specific nodes
kind load docker-image myapp:v1.1 --name multi --nodes kind-worker,kind-worker2
```

For CI, you often load from a tar archive:

```bash
docker save myapp:v1.0 > myapp.tar
kind load image-archive myapp.tar --name multi
```

After loading, the image is available in the cluster without a registry.

## extraMounts and kubeadm patches

Mount a host directory into a node — useful for a local registry or fixture files:

```yaml
nodes:
- role: control-plane
  extraMounts:
  - hostPath: /tmp/registry
    containerPath: /var/lib/registry
```

Tune kubelet or kube-proxy via kubeadm patches:

```yaml
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "env=test"
  - |
    kind: kube-proxy
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    mode: ipvs
```

If the API server port 6443 is already taken, set another in the cluster config:

```yaml
networking:
  apiServerPort: 6444
```

## kind with kubeconfig

By default, kind merges the context into `~/.kube/config`. For isolation:

```bash
# Separate kubeconfig
KUBECONFIG=~/.kube/kind-config kind create cluster --name isolated

# Or export after creation
kind get kubeconfig --name multi > ./kubeconfig
export KUBECONFIG=./kubeconfig
kubectl get nodes
```

Managing multiple clusters:

```bash
kind get clusters
# kind
# multi
# isolated

# Delete specific one
kind delete cluster --name isolated
```

## Cleanup

```bash
kind delete cluster --name multi
# Deleting cluster "multi" ...
```

Without `--name`, the default cluster (`kind`) is deleted. All Docker resources are removed with the nodes. If Docker was stopped while the cluster was running, nodes remain in `NotReady` on the next start. Fix by recreating the cluster.

## Gotchas

**containerd inside the node.** kubectl talks to containerd directly. Familiar `docker ps` and `docker exec` won't show pods — they live inside the kind node. For debugging:

```bash
docker exec -it multi-control-plane crictl ps
docker exec -it multi-control-plane crictl logs <container-id>
```

**HostNetworking and PortMappings.** For external access to pods, you need `extraPortMappings` in the config. Without it, hostPort does not work — pod networking in kind is isolated.

**PersistentVolumes.** kind creates a `kind-node` StorageClass backed by local-path-provisioner. Data lives on the host at `/var/local-path-provisioner`. For a clean environment, just delete the cluster.

**cgroups v2.** In newer distros (Ubuntu 22.04+, Fedora), Docker may require configuration:

```bash
docker info | grep cgroup
# Cgroup Driver: systemd
# Cgroup Version: 2
```

kind works with both versions, but rare issues with memory limits occur with cgroups v2 on Arch Linux. Solution — pass in the config:

```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  "MemoryManager": true
runtimeConfig:
  "memorymanager.k8s.io/v1alpha1": true
```

**Versions.** kind lags behind upstream Kubernetes by 1-2 minor versions. Check the compatibility matrix in the project README before setting up a prod-like environment.

**Port already allocated.** kind could not bind the API server or a mapped host port. Check that 6443 is free, or change `networking.apiServerPort`. For Ingress, `extraPortMappings` must not collide with host services.

**No space left on device.** Docker ran out of disk. Clean unused images, then recreate the cluster:

```bash
docker system prune -a
kind delete cluster --name multi
```

---

kind is a fast way to spin up Kubernetes on a developer machine or in CI without virtualization. Main loop: `kind create cluster`, work, `kind delete cluster`. For air-gapped or multi-node scenarios — YAML config and `kind load docker-image`. Known limitations: no GPU support, no real network stack, performance below bare metal. For everything else — it works.
