K8s Basics #2: Local Environments — minikube / kind / Docker Desktop k8s
#1 What is Kubernetes sketched the big picture of a cluster split into control plane and workers. This post compares the three ways to bring up a K8s cluster on your laptop — minikube, kind, and Docker Desktop’s built-in K8s — installs kubectl, and walks through bringing up your first cluster with kind, all the way to inspecting nodes and system pods.
This series is K8s Basics, 7 posts.
- #1 What is Kubernetes — why do we need a container orchestrator?
- #2 Local environments — minikube / kind / Docker Desktop k8s ← this post
- #3 kubectl and your first Pod
- #4 Deployment / ReplicaSet
- #5 Service — ClusterIP / NodePort / LoadBalancer
- #6 ConfigMap / Secret
- #7 Namespaces and labels
By the end of this post you’ll have a local cluster running plus kubectl ready to inspect it. That’s the starting point from #3 onward.
Three ways to run K8s locally #
All three tools do the same thing — bring up a K8s cluster locally — but their internals and trade-offs are slightly different.
| Tool | How it works | Multi-node | Startup time | Best for |
|---|---|---|---|---|
| Docker Desktop k8s | A single-node K8s inside Docker Desktop’s VM | No (single node) | Average | macOS / Windows users already on Docker Desktop |
| minikube | Pick a VM or container driver to run the cluster | Yes (optional) | Average | When you want a rich set of addons (ingress, dashboard, etc.) |
| kind | Each Docker container is a K8s node | Yes | Fast | Lightweight, multi-node defined in YAML, or CI |
A bit more on each:
Docker Desktop k8s is the lowest-friction path on macOS and Windows. If you’re already on Docker Desktop, one checkbox in the Kubernetes tab brings up the cluster, and kubectl’s context switches to docker-desktop automatically. The downside: it’s macOS / Windows only (Linux Docker Desktop offers the same option, but environments vary), and since it’s K8s running inside a VM that Docker Desktop already runs, it eats a fair amount of memory and CPU.
minikube is one of the older tools in this category. Depending on the environment, it picks a driver — docker / hyperkit / kvm / virtualbox — automatically or lets you choose. The biggest selling point is its rich addon system. Things you commonly need — ingress, metrics-server, dashboard, registry — flip on with a single command.
kind’s name describes its behavior — Kubernetes IN Docker. A Docker container is used directly as a K8s node, so it’s lighter and faster than tools that spin up a VM (minikube, Docker Desktop). And multi-node clusters can be defined in a short YAML, which is great for mimicking control plane / worker separation. K8s’s own CI uses kind for that reason.
Which one to pick #
If you’re already on Docker Desktop on macOS or Windows, Docker Desktop k8s is the easiest start. One checkbox brings up the cluster and it slots in with the Docker setup you already use. On Linux, or if you want to mimic multi-node setups, or if you want to create and tear down clusters often, kind fits well. If you want to flip addons on and off as you experiment, minikube is also a solid pick.
This series uses kind as the main path. It’s lightweight and fast, and you can create or destroy clusters with one command — perfect for learning. That said, the kubectl commands from #3 onward are identical no matter which one you pick. Only the way you bring up the cluster is different; the K8s resources you work with on top are the same. So pick what fits your environment and follow along.
Installing kubectl #
Wherever you bring up the cluster, the client that talks to it is the same — kubectl. It’s a CLI that sends commands over HTTP to the K8s control plane’s API server (kube-apiserver).
macOS — Homebrew #
brew install kubectlWindows — winget or Chocolatey #
winget install -e --id Kubernetes.kubectl
# or
choco install kubernetes-cliLinux — official binary #
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/You can also install via package managers (apt, dnf, etc.) on most distros, but K8s is more comfortable when client and server versions stay within a compatible range. For exact steps see the installation guide on kubernetes.io.
After installing, check the client version:
kubectl version --clientClient Version: v1.30.x
Kustomize Version: v5.x.xThere’s no cluster yet, so the server version isn’t shown. That’ll show up once a cluster is running.
Bring up your first cluster with kind #
This is the main path of this post. Install kind first.
brew install kindwinget install -e --id Kubernetes.kind
# or
choco install kindcurl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x kind
sudo mv kind /usr/local/bin/kind uses the Docker daemon. Docker Desktop or the Docker Engine has to be running first.
Now create a cluster. For a single node, one command is enough.
kind create clusterCreating cluster "kind" ...
- Ensuring node image (kindest/node:v1.30.x) -
- Preparing nodes -
- Writing configuration -
- Starting control-plane -
- Installing CNI -
- Installing StorageClass -
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kindThe last line is what matters. kubectl’s context switches to kind-kind automatically. Now your kubectl commands target this cluster.
How kind actually works #
We said kind uses a Docker container as a K8s node. You can confirm that on the Docker side.
docker psCONTAINER ID IMAGE COMMAND PORTS NAMES
abc123def456 kindest/node:v1.30.x "/usr/local/bin/entr…" 127.0.0.1:xxxxx->6443/tcp kind-control-planeA single container built from the kindest/node image is running, and inside it both control plane and worker run together. A random host port is mapped to the container’s 6443 (the K8s API server’s default port), and that’s where kubectl sends commands.
First commands — peek at nodes and system pods #
Now that the cluster is up, let’s see the components from #1 — the ones we only knew by name — actually running.
kubectl get nodesNAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 45s v1.30.xA single-node cluster, so just one row. This one node both serves as control plane and accepts workloads (a common shape in local environments).
Next, look at pods across all namespaces. K8s components themselves often run as pods on the cluster.
kubectl get pods -ANAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-xxxxxxxxxx-aaaaa 1/1 Running 0 1m
kube-system coredns-xxxxxxxxxx-bbbbb 1/1 Running 0 1m
kube-system etcd-kind-control-plane 1/1 Running 0 1m
kube-system kindnet-xxxxx 1/1 Running 0 1m
kube-system kube-apiserver-kind-control-plane 1/1 Running 0 1m
kube-system kube-controller-manager-kind-control-plane 1/1 Running 0 1m
kube-system kube-proxy-xxxxx 1/1 Running 0 1m
kube-system kube-scheduler-kind-control-plane 1/1 Running 0 1m
local-path-storage local-path-provisioner-xxxxxxxxxx-yyyyy 1/1 Running 0 1mThe components from #1 are right there — kube-apiserver, etcd, kube-scheduler, kube-controller-manager, kube-proxy, plus the cluster DNS coredns. Add to that kind’s network plugin kindnet, and local-path-provisioner, which fakes PersistentVolume in local environments.
The kube-system namespace is where K8s puts the pods it uses for its own operations. The apps we’ll create from now on live in default (or in a namespace we make ourselves — see #7).
Finally, pull the cluster’s meta info.
kubectl cluster-infoKubernetes control plane is running at https://127.0.0.1:xxxxx
CoreDNS is running at https://127.0.0.1:xxxxx/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.The IP and port on the first line is the same one as the port mapping from docker ps above. So kubectl is reaching kube-apiserver inside the node container through the port Docker exposed on the host.
kubeconfig — where kubectl’s commands go #
Where kubectl sends commands is decided by the kubeconfig file. The default path:
- macOS / Linux —
~/.kube/config - Windows —
%USERPROFILE%\.kube\config
This file holds three things:
| Section | What it is |
|---|---|
| clusters | What clusters exist (API server URL, CA cert) |
| users | What credentials to use against each cluster (cert, token, etc.) |
| contexts | A bundle of “this cluster + this user + this namespace.” kubectl uses one context at a time |
When kind creates a cluster, it adds a kind-kind context and points the current context at it. Confirm that.
kubectl config get-contextsCURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kind-kind kind-kind kind-kind kubectl config current-contextkind-kindOnce you start working with multiple clusters at once (local kind, a company dev cluster, a company prod cluster), kubectl config get-contexts shows several entries. To switch the current one, use use-context.
kubectl config use-context kind-kindWhen you’re moving between a company cluster and a local one, the habit of always being aware of your current context matters — so you don’t accidentally send a command to the wrong cluster. Tools that surface the current context in your shell prompt (kube-ps1, starship, etc.) are common.
If you’re using Docker Desktop k8s instead #
If you’re already on Docker Desktop, one toggle in settings brings up the cluster. The exact menu path varies by version, but roughly:
- Open Docker Desktop → Settings (or Preferences)
- Click the Kubernetes tab in the left menu
- Check Enable Kubernetes → Apply & Restart
The first activation takes a few minutes while it pulls K8s component images. When it finishes, the kubectl context is added automatically.
kubectl config use-context docker-desktop
kubectl get nodesNAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane 2m v1.30.xEvery kubectl command after this is identical to kind.
If you’re using minikube instead #
Same idea. Install the minikube CLI (official guide: minikube.sigs.k8s.io) and start a cluster.
minikube startTo pick a driver explicitly, use --driver. The default is auto-selected based on the environment — docker / hyperkit / kvm, etc.
minikube start --driver=dockerWhen minikube comes up, the context switches to minikube.
kubectl config use-context minikube
kubectl get nodesminikube’s selling point — addons — is managed with minikube addons. To turn on the ingress controller, for example:
minikube addons enable ingressCleaning up #
Knowing how to clean up is useful after a learning session, or whenever you want a fresh start.
kind
kind delete clusterIf you didn’t pass a name, it deletes the cluster with the default name (kind). If you’ve been running multiple clusters, specify with --name.
Docker Desktop k8s
Uncheck Enable Kubernetes in the Kubernetes tab in settings. Or use Reset Kubernetes Cluster on the same screen to reset.
minikube
minikube stop # pause (you can start again later)
minikube delete # full deleteA big upside of local clusters is the lightness of being able to delete and recreate them anytime. When something feels tangled, wiping clean and bringing up a fresh cluster is often faster than debugging.
Summary #
What this post pinned down:
- Three ways to run K8s locally — Docker Desktop k8s (one checkbox, macOS / Windows), minikube (rich addons), kind (lightweight, fast, multi-node).
- The client is always the same —
kubectl. Install via a package manager or the official binary, then verify withkubectl version --client. - kind uses one Docker container as a node.
kind create clusterbrings up a single-node cluster, and you can see thekindest/nodecontainer indocker ps. kubectl get nodes/kubectl get pods -A/kubectl cluster-infoshow the components mentioned in #1 actually running —kube-apiserver,etcd,coredns, and so on.- kubeconfig (
~/.kube/config) has three sections — clusters / users / contexts — andkubectl config use-contextis how you switch between clusters. - Tear down with
kind delete cluster/minikube delete/ Docker Desktop’s Disable Kubernetes when you’re done.
Next — bring up your first Pod with kubectl #
This post laid the starting point — a local cluster plus kubectl connected to it. The next post brings up the first Pod on this cluster.
In #3 kubectl and your first Pod we look at the smallest execution unit in K8s — the Pod. We’ll see both the imperative path (kubectl run) and the declarative path (a YAML manifest with kubectl apply), and walk through the everyday commands kubectl get / describe / logs / exec to inspect Pods. That’s where K8s really starts.