Skip to content

Quick Start

Terminal window
kinder create cluster

You should see output like:

Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.35.1) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Installing MetalLB
✓ Installing Metrics Server
✓ Tuning CoreDNS
✓ Installing Local Path Provisioner
✓ Installing Envoy Gateway
✓ Installing Dashboard
✓ Installing Local Registry
✓ Installing cert-manager
Addons:
* MetalLB installed
* Metrics Server installed
* CoreDNS Tuning installed
* Local Path Provisioner installed
* Envoy Gateway installed
* Dashboard installed
* Local Registry installed
* cert-manager installed
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind

kinder also prints a dashboard token and port-forward command — save these for later.

AddonNamespacePurpose
MetalLBmetallb-systemLoadBalancer IP assignment
Envoy Gatewayenvoy-gateway-systemGateway API ingress
Metrics Serverkube-systemkubectl top support
CoreDNS tuningkube-systemOptimised DNS caching
Local Path Provisionerlocal-path-storageAutomatic dynamic PVC provisioning with local-path as default StorageClass
Headlampkube-systemWeb UI for cluster inspection
Local Registrydefault (host network)Private container registry at localhost:5001
cert-managercert-managerAutomatic TLS certificates with self-signed ClusterIssuer

Deploy a LoadBalancer service and confirm it gets an external IP:

Terminal window
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --type=LoadBalancer --port=80
kubectl get svc nginx

You should see a real IP under EXTERNAL-IP:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.96.150.34 172.19.255.200 80:30693/TCP 7s

Clean up:

Terminal window
kubectl delete svc nginx && kubectl delete deployment nginx

Check that kubectl top returns real CPU and memory data:

Terminal window
kubectl top nodes

Expected output:

NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
kind-control-plane 168m 0% 1385Mi 8%

Verify that autopath and cache 60 are present in the Corefile:

Terminal window
kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}'

Look for these entries in the output:

autopath @kubernetes
cache 60 {
disable success cluster.local
disable denial cluster.local
}

Confirm the GatewayClass is accepted:

Terminal window
kubectl get gatewayclass eg

Expected output:

NAME CONTROLLER ACCEPTED AGE
eg gateway.envoyproxy.io/gatewayclass-controller True 9m

Port-forward to the dashboard:

Terminal window
kubectl port-forward -n kube-system service/headlamp 8080:80

Open http://localhost:8080 and paste the token that was printed during cluster creation.

If you need to retrieve the token later:

Terminal window
kubectl get secret kinder-dashboard-token -n kube-system \
-o jsonpath='{.data.token}' | base64 -d

Confirm the registry container is running and accessible:

Terminal window
docker ps --filter name=kind-registry

Expected output:

CONTAINER ID IMAGE COMMAND PORTS NAMES
abc123def456 registry:2 "/entrypoint.sh /etc…" 0.0.0.0:5001->5000/tcp kind-registry

Verify dev tool discovery ConfigMap is present:

Terminal window
kubectl get configmap local-registry-hosting -n kube-public

Expected output:

NAME DATA AGE
local-registry-hosting 1 60s

Check all three cert-manager components are running:

Terminal window
kubectl get pods -n cert-manager

Expected output:

NAME READY STATUS RESTARTS AGE
cert-manager-... 1/1 Running 0 60s
cert-manager-cainjector-... 1/1 Running 0 60s
cert-manager-webhook-... 1/1 Running 0 60s

Confirm the self-signed ClusterIssuer is ready:

Terminal window
kubectl get clusterissuer selfsigned-issuer

Expected output:

NAME READY AGE
selfsigned-issuer True 60s

To load a locally-built image into every node of the cluster, use kinder load images:

Terminal window
docker build -t myapp:dev .
kinder load images myapp:dev

This works with all three providers (docker, podman, nerdctl) and skips re-importing if the image is already present on every node. See the Load Images CLI reference for full details.

Once your cluster is up, kinder gives you a small set of lifecycle verbs for daily iteration:

Terminal window
# Free CPU/RAM without losing state — pods, PVCs, services all survive
kinder pause my-cluster
kinder resume my-cluster
# Capture a complete snapshot (etcd + images + PV contents) for instant reset
kinder snapshot create my-cluster baseline
kinder snapshot list my-cluster
kinder snapshot restore my-cluster baseline
# See cluster + per-node container state
kinder status my-cluster

Snapshots refuse to restore across mismatched Kubernetes versions, topologies, or addon versions, so the captured state is always consistent. See the changelog v1.5 entry for the full surface.

Skip the manual build → push → rollout loop:

Terminal window
kinder dev --watch ./src --target myapp

kinder dev watches the directory, builds a Docker image on every change, loads it into every node via the kinder load images pipeline, and rolls the target Deployment automatically — printing per-step timing per cycle. Use --poll on Docker Desktop for macOS where fsnotify events are unreliable. Pair with the Local Dev Workflow guide for the full inner-loop pattern.

Run kinder doctor to check prerequisites and identify issues:

Terminal window
kinder doctor

This checks that Docker (or Podman/nerdctl), kubectl, and other dependencies are installed and reachable.

For cryptic errors that surface AFTER cluster creation (kubelet stuck, addon won’t roll out, image pull mysteriously failing), kinder doctor decode scans recent docker logs and kubectl get events against a catalog of known patterns and prints plain-English explanations with suggested fixes:

Terminal window
kinder doctor decode --since 30m

See the Troubleshooting reference for the full catalog and --auto-fix whitelist.

When you are done:

Terminal window
kinder delete cluster # deletes the default "kind" cluster
kinder delete cluster my-cluster # delete by positional name
kinder delete cluster --name my-cluster # or by --name flag