Tools Used in Kubernetes · kind

kind (Kubernetes in Docker)

kind runs a full Kubernetes cluster on your laptop by turning ordinary Docker containers into Kubernetes nodes — one container is the control plane, more containers are workers, and kubeadm bootstraps each one exactly as it would a real machine. It was built by Kubernetes SIG Testing to run the project's own upstream conformance suite without needing cloud credentials or real hardware, and that origin still shows: a fresh multi-node cluster comes up in under a minute, and tearing it down leaves nothing behind but a stopped container. Today it's the default way most engineers get a disposable, close-to-real cluster for local development, and it's just as commonly the cluster CI pipelines create fresh on every run to execute a Helm chart, an integration test, or a policy check against real API objects instead of a mock. It is not a production runtime, and it doesn't try to be — knowing exactly where that line sits is most of what this page is about.

☺ Explain it like I'm 10

Imagine you want to practice being mayor of a whole town, but building a real town is expensive and slow. So instead you set up a row of cardboard boxes on the kitchen table — one box says "City Hall" on it, a few others say "Fire Station" and "School." Each box acts like a real building well enough to practice with: you can walk your toy people between them, knock one down and watch what happens to the town, add a new box for a new building. When you're done practicing, you fold the boxes flat and the kitchen table is clean again — nothing was ever really built, so nothing real has to be un-built. kind is the box trick for Kubernetes: each cardboard box is a Docker container standing in for a whole computer, good enough to practice the real thing on, gone the instant you're done.

🐰Your host for this topic: Remy the Rabbit — nobody on the Squad hits "reset and go again" faster than Remy, and a kind cluster is up, tested, and torn down before slower tools have finished pulling their first image.

What kind actually is, and why kubeadm is underneath it

☺ Like you're 10: Each container pretends to be a whole separate computer, and the same setup script real machines use — kubeadm — is what turns each pretend computer into a real Kubernetes node.

A kind "node" is a single Docker container running a specially built image, kindest/node, that carries a systemd init process, a container runtime (containerd), and the Kubernetes binaries for one specific version — the image tag is the Kubernetes version, e.g. kindest/node:v1.31.0. On cluster creation, kind picks one container to be the control-plane node and runs kubeadm inside it exactly the way you'd run it on bare metal, then runs kubeadm join inside each worker container to attach it. This is the detail that makes kind trustworthy rather than a toy: nothing about the API server, the scheduler, or kube-proxy is faked or reimplemented — you're running the genuine upstream control plane described in Kubernetes Architecture, just with node isolation coming from Docker's namespaces and cgroups instead of separate physical or virtual machines.

All of a kind cluster's containers sit on one Docker bridge network and can reach each other by container name, which is also how a control-plane container reaches a worker to schedule a Pod onto it. Docker Desktop, Docker Engine on Linux, and (experimentally, via KIND_EXPERIMENTAL_PROVIDER=podman) Podman can all host a kind cluster — the only hard requirement is a working container runtime with enough CPU and memory handed to it, since every node you ask for draws from that same pool.

Docker host machine — one Docker network kindest/node — control-plane kubeadm init kubelet · containerd · API server kindest/node — worker kubeadm join kindest/node — worker kubeadm join kindest/node — worker ingress-ready=true join your laptop's kubectl :6443 localhost :80 / :443 extraPortMappings every node is a container — Docker's isolation stands in for separate machines
⚠ Not for production

Every node shares the CPU, memory, and kernel of one Docker host, container-to-container networking has none of a real CNI's cross-machine routing to prove out, and the whole cluster disappears the moment you run kind delete cluster or reboot the host with no persistence configured. kind is honest about this in its own docs. Treat it as a fast, disposable stand-in for local development, CI, and learning — never as somewhere a real workload runs.

The kind config file: multi-node, custom CNI, ingress

☺ Like you're 10: One YAML file says how many pretend computers you want, which jobs they each do, and which doors on your real computer should open into them.

A one-line kind create cluster gives you a single all-in-one node, fine for a quick smoke test. Everything past that goes in a kind.x-k8s.io/v1alpha4 Cluster config, passed with --config, which is where multi-node topology, a swapped-out CNI, and ingress-ready port mappings all live:

# kind-config.yaml — 1 control plane, 2 workers, custom CNI, ingress ports
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: dev

networking:
  disableDefaultCNI: true          # skip kindnet — you'll install Calico/Cilium yourself
  podSubnet: "10.244.0.0/16"       # must match what your CNI manifest expects
  serviceSubnet: "10.96.0.0/16"

nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            node-labels: "ingress-ready=true"
    extraPortMappings:               # host → this node's container, for ingress-nginx
      - containerPort: 80
        hostPort: 80
        protocol: TCP
      - containerPort: 443
        hostPort: 443
        protocol: TCP
  - role: worker
  - role: worker

containerdConfigPatches:             # route a private registry through localhost:5001
  - |-
    [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5001"]
      endpoint = ["http://kind-registry:5000"]

kubeadmConfigPatches and kubeadmConfigPatchesJSON6902 reach straight into the kubeadm InitConfiguration/ClusterConfiguration objects kind generates for you — the same shape you'd hand-write for a real cluster, covered in the kubeadm tool guide. That's how the control-plane node above gets labeled ingress-ready=true: kindnet and the stock config don't know or care about ingress controllers, so the label is how ingress-nginx's kind-specific manifest later finds the right node to schedule onto.

CNI and ingress in a kind cluster

☺ Like you're 10: kind comes with a simple built-in network already working, but you can swap it out for a real one if that's what you're actually testing.

By default every kind cluster ships kindnet, a minimal CNI good enough for Pod-to-Pod connectivity and not much else — no NetworkPolicy enforcement, no BGP, none of the eBPF dataplane features a real deployment might depend on. That's fine for testing a Deployment or a Helm chart, and it's exactly why disableDefaultCNI: true exists: set it, then apply a real CNI manifest — Calico or Cilium — right after cluster creation, matching its expected pod CIDR to your podSubnet. This is the standard way Networking & CNI and Multi-Cluster & Fleet Management both recommend proving out NetworkPolicy behavior or multi-cluster mesh federation without touching a cloud account.

Ingress works the same deliberate way: nothing routes traffic from your laptop into the cluster until you wire it up. The pattern above — one node labeled ingress-ready=true plus extraPortMappings for 80 and 443 on that same node — is exactly what ingress-nginx's official kind deployment manifest expects: it carries a nodeSelector for that label and a toleration for the control-plane taint, so it schedules onto the one node whose container actually has those host ports mapped out. Once that's applied, curl localhost/ on your own machine reaches an Ingress object inside the cluster, no port-forward required.

⚠ LoadBalancer Services still stay pending

Nothing above touches type: LoadBalancer. As Services & Networking covers, EXTERNAL-IP only leaves <pending> when something is actively watching for LoadBalancer Services and provisioning for them — a cloud controller manager on a real cloud, or on kind specifically, the community cloud-provider-kind project (or MetalLB) if you install it yourself. Out of the box, a kind cluster has no LoadBalancer implementation at all; that's expected, not broken.

Day-to-day commands

☺ Like you're 10: A handful of commands cover almost everything: make a cluster, get an image into it, look inside it, throw it away.

The one command that trips people up first: kind clusters don't automatically see images sitting in your local Docker cache the way you might expect, because each node container has its own isolated containerd image store. kind load is how an image gets from your machine into every node's store without going through a registry at all.

# --- create, inspect, and remove clusters ---
$ kind create cluster --config kind-config.yaml    # named "dev" per the config above
$ kind get clusters                                  # every kind cluster on this host
$ kind get nodes --name dev                          # the container name of every node
$ kubectl config get-contexts                        # kind writes/merges "kind-dev" for you
$ kind delete cluster --name dev                      # gone — containers stopped and removed

# --- get a locally built image INTO the cluster, no registry needed ---
$ docker build -t myapp:dev .
$ kind load docker-image myapp:dev --name dev         # pushes into every node's containerd store
$ kind load image-archive myapp.tar --name dev        # same, from a saved tarball (airgapped CI)

# --- look inside a node like it's a real machine ---
$ docker exec -it dev-control-plane crictl ps         # containerd's CLI, not docker ps
$ docker exec -it dev-worker journalctl -u kubelet -f # kubelet's systemd logs, live

# --- capture cluster state before tearing it down ---
$ kind export logs ./kind-logs --name dev             # every component's logs, pre-delete
$ kind export kubeconfig --name dev                    # re-merge if the context ever goes stale

That last habit — kind export logs right before kind delete cluster — is worth building early. Once a cluster is gone its logs are gone with it; there's no "kubectl logs on a container that no longer exists."

Using kind in CI

☺ Like you're 10: Every test run gets its own brand-new cluster, used once, then thrown away — so one run's mess can never leak into the next.

This is where kind earns most of its keep in practice. A CI runner is already an ephemeral, throwaway environment, and a kind cluster fits that model exactly: create it fresh at the start of the job, run real kubectl and Helm commands against a real API server, delete it (or just let the runner get recycled) at the end. No shared cluster means no state from one pull request's test run can contaminate the next one's — the single biggest source of "works on my machine, flakes in CI" that a shared staging cluster tends to accumulate.

# .github/workflows/test.yml — matrix-tested against three Kubernetes minors
jobs:
  e2e:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        k8s-version: ["v1.29.8", "v1.30.4", "v1.31.0"]
    steps:
      - uses: actions/checkout@v4
      - uses: helm/kind-action@v1
        with:
          node_image: kindest/node:${{ matrix.k8s-version }}
          config: ./ci/kind-config.yaml
          cluster_name: ci

      - run: docker build -t myapp:${{ github.sha }} .
      - run: kind load docker-image myapp:${{ github.sha }} --name ci

      - run: helm upgrade --install myapp ./chart
                 --set image.tag=${{ github.sha }} --wait --timeout 90s
      - run: kubectl wait --for=condition=available deploy/myapp --timeout=60s
      - run: ./run-integration-tests.sh

      - if: failure()
        run: kind export logs ./kind-logs --name ci      # the artifact worth uploading on red
kind create cluster kind load image apply & test helm · kubectl export logs only on failure kind delete cluster next CI run nothing survives between runs — every job starts from zero
◆ Key idea

The node_image matrix above is the other reason CI teams reach for kind specifically: kindest/node images are published and pinned per Kubernetes minor version, so testing a Helm chart or an admission webhook against three supported Kubernetes versions in parallel is a one-line matrix, not three separately provisioned cloud clusters. It's the same trick that lets kind test Kubernetes itself against every minor it still supports.

Gotchas and limits

☺ Like you're 10: A few things catch almost everyone once: images that "aren't there," storage that vanishes, and a real exam that isn't run this way at all.

Images don't cross the boundary by themselves. Covered above, but worth repeating because it's the single most common first-timer confusion: docker build populates your host's Docker image cache, not any node's containerd store, and a Pod referencing that image with no registry behind it will sit in ImagePullBackOff until you run kind load docker-image. Setting imagePullPolicy: Never or IfNotPresent on the Deployment matters too — the default Always will try to pull from a registry even for an image kind just loaded locally.

Storage doesn't survive the cluster. kind ships a default StorageClass backed by Rancher's local-path-provisioner, which is genuinely useful for testing a StatefulSet's PVC lifecycle end to end — see Storage & CSI — but the actual bytes live inside the node container's own filesystem. kind delete cluster takes every PersistentVolume's data with it; there's no separate disk to reattach to a new cluster.

Resource pressure isn't representative. Every node container draws CPU and memory from the same Docker host pool, so a five-node kind cluster on a laptop with 8 vCPUs doesn't behave like five real machines with 8 vCPUs each — it behaves like one machine's worth of resources, sliced five ways, contending with whatever else is running on your laptop at the time. Don't trust eviction behavior, scheduler pressure, or resource-request tuning you observed on kind to transfer directly to real hardware; Scheduling & Resource Management is explicit about that gap.

It isn't the CKA/CKAD/CKS exam environment. Those exams run against a pre-provisioned remote cluster you reach through a browser-based terminal — you don't build the cluster yourself, and kind isn't installed there. That doesn't make kind a bad way to prepare; the API objects, kubectl behavior, and failure modes you practice against a kind cluster are the genuine upstream Kubernetes ones. It just means "set up a kind cluster" is never itself an exam task. This course's own CKA practice tasks and CKAD practice tasks are written to run on exactly this kind of throwaway cluster.

kind vs. minikube vs. k3d: picking a local cluster tool

☺ Like you're 10: They all give you a practice cluster — they just disagree about what a "node" pretends to be.

All three exist to solve the same problem — a real, standards-conformant Kubernetes API to develop and test against, without a cloud bill — and the honest answer for which to reach for is usually "whichever one your team already standardized on," because the differences are real but narrow.

Dimensionkindminikubek3d
What a "node" isA Docker container running full upstream kubeadm-bootstrapped KubernetesTypically one VM (or container driver), a more complete single-machine simulationA Docker container running k3s, Rancher's trimmed-down Kubernetes distribution
Multi-node by defaultYes — the whole point of its config fileSupported, less commonly used that wayYes, similarly config-driven
Extras includedNone — deliberately minimal, you install what you testAddons: dashboard, metrics-server, ingress, registry, one command eachWhatever k3s bundles (Traefik, local-path, CoreDNS) unless disabled
Startup speedFast — seconds to low tens of secondsSlower with a VM driver; comparable with a container driverFast — comparable to kind, sometimes faster given k3s's smaller footprint
Closest to "real" upstream KubernetesYes — same binaries, same kubeadm pathYes, with the Docker/container driver; VM drivers add a layerNo — k3s trims and swaps several components for size
Where it shinesCI matrices, conformance-style testing, exam practiceA single richer local dev box with batteries includedVery fast, very light iterative local dev

The practical rule most teams land on: reach for kind when you want the closest thing to real upstream Kubernetes behavior for CI or certification practice, reach for minikube when you want a friendlier single-machine dev experience with common addons a command away, and reach for k3d when startup speed and a tiny footprint matter more than being byte-for-byte identical to a stock control plane. Nothing stops a team from using more than one — kind in CI, minikube on individual laptops — since all three speak the same Kubernetes API underneath.

✎ Try it

Save the kind-config.yaml from earlier on this page and run kind create cluster --config kind-config.yaml. Confirm you got three nodes with kubectl get nodes, then apply Calico's manifest and watch every node reach Ready only after its CNI Pod does. Build a tiny "hello" container image locally, kind load docker-image it in, and deploy it with imagePullPolicy: Never — watch it start instantly with no registry involved. Then install ingress-nginx's kind manifest, create an Ingress for your hello app, and hit curl localhost/ from your own terminal. Finally, run kind export logs ./logs, open the folder, and see just how much every component was already logging that kubectl alone never shows you.

🐰 Remy's-eye view

"People ask me why I don't just keep one kind cluster running all week and reuse it. Because reuse is exactly the risk — a stale ConfigMap from Tuesday's test, a CRD nobody cleaned up, a NetworkPolicy left over from a debugging session, and suddenly Friday's 'passing' test run was only passing against Tuesday's leftovers, not against what the manifest actually says today. kind create, run the thing, kind delete. Every single time. The half-second I save by keeping a cluster warm isn't worth the afternoon I'd lose chasing a bug that only exists because of state I forgot was there."

🎬 At the Pod Squad
🐰

Remy the Rabbit: Cluster up, chart installed, tests green, cluster gone — forty seconds, start to finish. Next.

🤖

Recon the Robot: I clocked it at forty-one. I'm not complaining. A CI job that starts from zero every single time is the only kind of reconcile loop I actually trust completely — nothing carried over for me to be wrong about.

👺

Gizmo: Or — hear me out — we just keep one kind cluster running forever on the build server. Skip the create step entirely! Think of the seconds we'd save!

🐢

Timmy the Turtle: The seconds you'd save today are the afternoon you'd lose next month, when a test passes because of a Secret somebody left in that cluster in March and nobody remembers applying. Ephemeral isn't slower, Gizmo. It's honest.

🦊

Foxy: And when it does break, it breaks the same way it would on a real cluster — real kubeadm, real API server. Half my troubleshooting instincts got built practicing on exactly this.

🐘

Ellie the Elephant: Just don't confuse fast-and-disposable with production-grade. I've watched one too many "it worked fine on kind" statements walk straight into a LoadBalancer stuck at pending, on a cluster that actually needed a cloud controller to answer.

🐰

Remy: Fair. Disposable practice ground, not a home. I'll take that trade for forty-one seconds every time.

🐢 Timmy's checkpoint

1. What is a kind "node" actually made of, and which real bootstrapping tool runs inside it to turn a plain container into a working Kubernetes node? 2. You build an image locally and reference it in a Deployment, and the Pod sits in ImagePullBackOff. What almost certainly went wrong, and what two things fix it? 3. Why does disableDefaultCNI: true exist, and what has to match between your config and the CNI manifest you apply afterward for Pod networking to actually come up? 4. A Service of type: LoadBalancer sits at <pending> on a fresh kind cluster. Is that a bug — and what would you install to change it? 5. Why is deleting and recreating a kind cluster for every CI run usually the right call, rather than reusing one long-lived cluster? 6. Is a kind cluster ever the actual CKA or CKAD exam environment — and if not, why is practicing on one still worthwhile?

Check your answers
  1. A kind node is a Docker (or Podman) container running the kindest/node image, which bundles containerd and the Kubernetes binaries; kubeadm runs inside that container — kubeadm init on the control-plane container, kubeadm join on each worker — exactly as it would on a real machine.
  2. Almost always a missing image: docker build only populates your host's Docker cache, not any node container's isolated containerd store. Fix it by running kind load docker-image <image> --name <cluster> to push it into every node, and set imagePullPolicy: Never (or IfNotPresent) so the kubelet doesn't try to pull it from a registry that doesn't have it.
  3. kind's default CNI, kindnet, is deliberately minimal — no NetworkPolicy enforcement and none of a real CNI's advanced features — so disableDefaultCNI: true lets you install Calico, Cilium, or another CNI to test against instead. The CNI manifest's expected pod CIDR has to match the podSubnet set in the kind config, or Pod networking won't come up correctly.
  4. Not a bug — kind ships with no LoadBalancer implementation at all out of the box, so EXTERNAL-IP stays <pending> until something is actively provisioning for it. Installing the community cloud-provider-kind project or MetalLB gives LoadBalancer Services a real external IP on a kind cluster.
  5. A fresh cluster per run guarantees no state — a leftover ConfigMap, CRD, or NetworkPolicy from a previous run — can make a test pass or fail for the wrong reason. Reusing one long-lived cluster trades a few seconds of setup time for exactly the kind of hidden, hard-to-reproduce state drift CI exists to prevent.
  6. No — CKA, CKAD, and CKS run on a pre-provisioned remote cluster reached through a browser-based terminal; you never build the cluster yourself, and "set up kind" is never an exam task. It's still worth practicing on, because kind runs the genuine upstream API server, scheduler, and kubelet behavior — the actual skills the exam tests transfer directly, even though the environment you build them on differs from the one you sit the exam in.