Skip to content
SRE & DevOpsHow-To Published Updated 5 min readViews unavailable

How to Set Up a Local Kubernetes Cluster with kind

Create a reproducible local Kubernetes cluster with kind, load development images safely, expose services, test multiple nodes, and clean up.

kind (“Kubernetes IN Docker”) runs a genuine, multi-node Kubernetes cluster locally, using Docker containers as the “nodes” — considerably lighter-weight than a full VM-based local cluster, and well suited to development and CI use.

Step 1: install kind and kubectl

brew install kind kubectl

(Or the equivalent for your platform — kind ships as a single static binary with no other dependencies beyond a working Docker installation.)

Pin the kind and kubectl versions used by CI and the team. Reproducing a test requires knowing both client and cluster versions. Confirm the container runtime has enough CPU, memory, and disk, and verify official checksums when installing a downloaded binary.

Step 2: create a basic single-node cluster

kind create cluster --name dev --wait 120s

This creates a complete Kubernetes control plane and a single worker node, both running as Docker containers, and automatically configures kubectl to point at it.

Step 3: verify it’s actually working

kubectl cluster-info --context kind-dev
kubectl get nodes

Step 4: create a more realistic multi-node cluster

For testing scheduling behavior, node affinity, or anything that genuinely needs more than one node to demonstrate:

# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
kind create cluster --name dev --config kind-config.yaml

If dev already exists, delete it or choose another name; kind does not mutate an existing cluster from a new configuration. To test a specific Kubernetes release, use a reviewed node image and digest published by kind. That makes local and CI failures comparable instead of following whichever default ships with a newly installed binary.

Step 5: build and load a local image without pushing anywhere

This is kind’s most practically useful feature for local development — testing a locally-built image without needing a registry at all:

docker build -t myapp:dev .
kind load docker-image myapp:dev --name dev

kind load docker-image copies the image directly into the cluster’s node containers, making it available to kubectl deployments without ever touching an external registry — genuinely useful for a fast local edit-build-test loop.

Give each build a unique tag, such as the source commit, instead of repeatedly overwriting myapp:dev. Kubernetes may keep a previously loaded image under the same reference, making a test appear to run new code when it does not. Verify that loading targeted the correct named cluster.

Step 6: deploy your application

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 2
  selector:
    matchLabels: {app: myapp}
  template:
    metadata:
      labels: {app: myapp}
    spec:
      containers:
        - name: myapp
          image: myapp:COMMIT_SHA
          imagePullPolicy: Never

imagePullPolicy: Never makes the local-only intent explicit. It guarantees failure if the image was not loaded to the node where the pod schedules, which is preferable to unexpectedly pulling a same-named public image. Do not copy this policy into production manifests that depend on a registry.

kubectl apply -f deployment.yaml

Step 7: expose the service and reach it locally

apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: NodePort
  selector: {app: myapp}
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
kubectl apply -f service.yaml

For a cluster created with a port mapping configured in kind-config.yaml (via extraPortMappings), this NodePort becomes directly reachable on localhost; otherwise, kubectl port-forward reaches it without any extra configuration:

kubectl port-forward service/myapp 8080:80

Port forwarding is bound to loopback by default and lasts only while the command runs. It is appropriate for development, not an ingress test. If the application depends on ingress behavior, install a controller explicitly supported by kind and map the required host ports in the original cluster configuration.

Step 8: iterate quickly

The practical development loop from here is: change code, rebuild the image, kind load docker-image again, then restart the deployment to pick up the new image:

docker build -t myapp:$(git rev-parse --short HEAD) .
kind load docker-image myapp:$(git rev-parse --short HEAD) --name dev
kubectl set image deployment/myapp myapp=myapp:$(git rev-parse --short HEAD)
kubectl rollout status deployment/myapp

Changing the image reference creates an auditable rollout; a restart with the same reference cannot prove which local bytes ran. Add readiness probes and a bounded smoke test. For CI, export diagnostics before deletion with kind export logs, because node and Kubernetes logs otherwise disappear with the containers.

Step 9: tear down when done

kind delete cluster --name dev

Deleting the cluster removes its containerized nodes and Kubernetes state. It does not necessarily reclaim every Docker image or unrelated volume on the host. Review disk usage separately and avoid broad prune commands on a shared machine.

What kind can and cannot prove

kind is excellent for API validation, controller tests, scheduling examples, and repeatable integration suites. It does not reproduce cloud load balancers, managed identity, provider storage, real multi-host networking, hardware failure, or production capacity. Keep provider-specific acceptance tests in an environment that actually supplies those features.

Record the kind node image version in CI rather than relying on an implicit latest choice. That makes Kubernetes-version changes deliberate, lets failures be reproduced locally, and prevents a routine cluster recreation from silently changing the control-plane behavior under test.

Why kind instead of a full VM-based local cluster

Because kind’s nodes are Docker containers rather than full virtual machines, cluster creation and teardown take seconds rather than minutes, and resource overhead is dramatically lower — making it practical to spin up and discard clusters routinely as part of everyday development or CI, rather than treating a local Kubernetes cluster as a heavyweight, rarely-recreated fixture.

Related:

Sources:

Comments