kind, minikube, and k3d each give you a Kubernetes API on a laptop. They differ in how long the first start takes, how they spend RAM, and how a LoadBalancer or Ingress becomes a port you can open. Create one cluster with the commands below and load a local image without pushing it to a registry.
Short answer
Use kind when the cluster should behave like upstream Kubernetes and when the same commands should run in CI. Use minikube when you want minikube addons enable ingress, a driver that is not Docker, or a tunnel that gives LoadBalancer Services an address. Use k3d when you want k3s, a small footprint, and a host port on the bundled load balancer without installing extra controllers.
All three talk to the Docker engine by default. docker info has to succeed. If it fails with a socket error, fix Docker socket permission denied before you debug the cluster tool. kubectl context names and everyday commands are in the kubectl cheat sheet.
Side by side
| kind | minikube | k3d | |
|---|---|---|---|
| What it runs | Upstream Kubernetes via kubeadm, one container per node | Upstream Kubernetes in a container or a VM | k3s in Docker |
| Default runtime | Docker | Docker, or a VM driver | Docker |
| Multi-node | Config file, role: worker |
--nodes |
--servers and --agents |
| Ingress | You install a controller | minikube addons enable ingress |
Traefik, unless you disable it |
| LoadBalancer | Pending until cloud-provider-kind or MetalLB | minikube tunnel (keep the process running) |
k3s ServiceLB plus the k3d proxy |
| Put a local image on the nodes | kind load docker-image |
minikube image load |
k3d image import |
| kubeconfig context | kind-<name> |
profile name (minikube by default) |
k3d-<name> |
| CI | Usual choice; no addon step | Works with --driver=docker; heavier |
Works; built-in balancer saves steps |
The first create waits on the node image download for all three. After that image is cached, kind and k3d usually reach Ready sooner than minikube, and a minikube VM driver also boots a guest OS. Treat that as an order, not a benchmark.
k3s runs the control plane as one process, so k3d is usually the lightest on RAM. kind runs the normal control-plane processes in the node container. A minikube VM driver reserves the --memory you set. The Docker driver sits closer to kind, plus any addon you enable.
Create a cluster
kind
brew install kind
winget install Kubernetes.kind
brew is macOS or Linux. winget is Windows. Chocolatey ships choco install kind.
One node:
kind create cluster --name dev
kubectl cluster-info --context kind-dev
kind delete cluster --name dev
More than one node, and host ports 80 and 443 aimed at the control-plane container, need a config file. kind has no --nodes flag.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443
- role: worker
- role: worker
kind create cluster --name dev --config kind-config.yaml
Pin the node image when you need a specific Kubernetes minor, for example --image kindest/node:v1.31.0. The tag has to exist for that kind release.
minikube
brew install minikube
winget install Kubernetes.minikube
minikube start -p dev --driver=docker --nodes=3 --cpus=2 --memory=4g
minikube status -p dev
minikube delete -p dev
--nodes=3 is one control plane plus two workers. --driver is docker or podman when you already have a container engine, or kvm2, qemu2, hyperv, virtualbox, or vfkit for a VM. --ha means several control-plane nodes, not “more workers.”
The profile name is the kubectl context: -p dev is kubectl config use-context dev. The default profile is minikube. Pin a release with --kubernetes-version=v1.31.0 or stable.
k3d
k3d needs Docker. macOS and Linux:
brew install k3d
Windows: choco install k3d or scoop install k3d. The project also publishes an installer:
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
k3d cluster create dev --agents 2 --port "8080:80@loadbalancer"
kubectl cluster-info --context k3d-dev
k3d cluster delete dev
--agents 2 adds two workers beside one server. The context is k3d-dev. With no name, the cluster is k3s-default and the context is k3d-k3s-default.
--port publishes a host port through the k3d proxy. Without it, k3d prints the high ports it chose. @server:0 is enough to disable Traefik on the first server:
k3d cluster create dev --agents 2 --k3s-arg "--disable=traefik@server:0" --port "8080:80@loadbalancer"
Install your own ingress controller after that, and point it at the published port.
Images, ingress, and LoadBalancer
Build with the Docker CLI, then copy the image into the nodes. A registry is not required for a single machine.
docker build -t example/web:1.4.0 ./web
kind load docker-image example/web:1.4.0 --name dev
minikube image load example/web:1.4.0 -p dev
k3d image import example/web:1.4.0 -c dev
kind accepts several tags in one call. minikube also accepts a tarball (minikube image load web.tar).
Avoid the tag latest. Kubernetes treats latest, and a missing tag, as imagePullPolicy: Always, so the kubelet still contacts a registry. Any other tag defaults to IfNotPresent. If the pull runs and the tag is not published, the Pod is ImagePullBackOff.
On current Docker, kind load docker-image can fail when the daemon uses the containerd image store (a digest error from ctr). Save one platform and load the archive instead. The platform has to match the node. An amd64 archive on an arm64 node fails at start with an exec format error.
docker image save --platform linux/amd64 --output web.tar example/web:1.4.0
kind load image-archive web.tar --name dev
Use linux/arm64 when the kind nodes are Apple Silicon.
Ingress and LoadBalancer are different doors:
- kind does not install an ingress controller or a cloud load balancer.
extraPortMappingsonly publishes container ports. Install the controller you use in production and bind it to 80 and 443 on that node. A LoadBalancer Service stays<pending>until one exists. Runcloud-provider-kindon the host (go install sigs.k8s.io/cloud-provider-kind@latest, then the binary, left in the foreground). It needs permission to open ports. MetalLB is the other option. - minikube enables ingress-nginx with
minikube addons enable ingress -p dev. For LoadBalancer, leaveminikube tunnel -p devrunning. Stopping it drops the routes. It may prompt for an administrator password to change host routes. - k3d installs Traefik and k3s ServiceLB. Reach them with
--port ...@loadbalancer. Disable Traefik before you install a second controller.
StorageClass names differ, which is why a manifest that worked on one laptop stays Pending on another. Check with kubectl get storageclass. A default kind cluster and a default minikube cluster call the class standard. A default k3d cluster calls it local-path. A claim that sets storageClassName to the wrong name does not bind. See Pod Pending / FailedScheduling.
When to pick kind
Pick kind to test against kubeadm, or to run the same create-load-apply steps in CI. Multi-node is a config file. Ingress and LoadBalancer are extra installs. A typical job is kind create cluster, kind load docker-image, kubectl apply, then kubectl wait.
kubectl wait --for=condition=available deployment/api --timeout=120s
When to pick minikube
Pick minikube when you want minikube addons list and one command for ingress, or a Hyper-V, KVM, or QEMU VM because Docker is not available. minikube tunnel replaces a cloud-provider process. It is a weaker CI default: the Docker driver works, but a tunnel is a long-running process a job rarely wants.
When to pick k3d
Pick k3d when RAM is tight, production is already k3s, or you want Traefik and a load balancer on the first create. The API is Kubernetes. Traefik, ServiceLB, local-path, and a slimmer control plane are k3s defaults, not kubeadm defaults.
Migration path
Moving between these tools does not export the cluster. The manifests in git are the source of truth.
- List images that are not in a registry (
docker images). You will load them again. - Free host ports 80 and 443 if you mapped them.
- Create the new cluster. Do not bind host ports 80 and 443 on two clusters at once.
- Load images with that tool’s command. A kind archive is not a
k3d image import. kubectl config use-contexttokind-dev,dev, ork3d-dev.kubectl apply -forkubectl apply -k. FixstorageClassNameif a PVC stays Pending, and the ingress class if traffic never arrives (nginxon the minikube addon,traefikon default k3d).- Delete the old cluster:
kind delete cluster --name dev,minikube delete -p dev, ork3d cluster delete dev.
On a shared cluster, push tags to a registry and drop host port maps. Docker Compose vs Kubernetes covers the application side: Compose on one engine, these tools for a local API, a real cluster when you have more than one machine.
FAQ
Which local Kubernetes tool is closest to a production cluster?
kind. Each node is a kubeadm cluster inside a container, which is the same bootstrap most upstream clusters use. k3d is a real Kubernetes API on k3s, with Traefik and a service load balancer already installed. minikube is also a real API; its addons and driver are the parts that will not match your production cluster.
How do I use a locally built image without a registry?
Load it into the cluster nodes before you apply. kind uses kind load docker-image, minikube uses minikube image load, and k3d uses k3d image import. Pin a tag other than latest, or set imagePullPolicy: IfNotPresent, or the kubelet will try the registry anyway.
Why does a LoadBalancer Service stay Pending?
Nothing on kind implements that Service type until you run cloud-provider-kind or install MetalLB. minikube needs minikube tunnel left running in another terminal. k3d ships with k3s ServiceLB and a proxy container, so the address appears once the cluster is up and you publish a host port.
Can I run kind, minikube, and k3d at the same time?
Yes, if Docker has the memory and host ports 80 and 443 are not claimed twice. Each tool writes its own kubeconfig context: kind-<name>, the minikube profile name, and k3d-<name>. Switch with kubectl config use-context.
How this guide was verified
Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .
Official sources read for this article

