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:
- How to Set Up Horizontal Pod Autoscaling in Kubernetes
- How to Set Up Centralized Logging for Kubernetes with the EFK Stack
Sources: