K8s Basics #2: Local Environments — minikube / kind / Docker Desktop k8s

10 min read

#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.

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.

ToolHow it worksMulti-nodeStartup timeBest for
Docker Desktop k8sA single-node K8s inside Docker Desktop’s VMNo (single node)AveragemacOS / Windows users already on Docker Desktop
minikubePick a VM or container driver to run the clusterYes (optional)AverageWhen you want a rich set of addons (ingress, dashboard, etc.)
kindEach Docker container is a K8s nodeYesFastLightweight, 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 #

Install kubectl (macOS)
brew install kubectl

Windows — winget or Chocolatey #

Install kubectl (Windows)
winget install -e --id Kubernetes.kubectl
# or
choco install kubernetes-cli

Linux — official binary #

Install kubectl (Linux)
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:

Verify install
kubectl version --client
Example output
Client Version: v1.30.x
Kustomize Version: v5.x.x

There’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.

Install kind (macOS)
brew install kind
Install kind (Windows)
winget install -e --id Kubernetes.kind
# or
choco install kind
Install kind (Linux)
curl -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.

Create cluster
kind create cluster
Example output
Creating 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-kind

The 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.

See the container kind brought up
docker ps
Example output
CONTAINER ID   IMAGE                  COMMAND                  PORTS                       NAMES
abc123def456   kindest/node:v1.30.x   "/usr/local/bin/entr…"   127.0.0.1:xxxxx->6443/tcp   kind-control-plane

A 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.

List nodes
kubectl get nodes
Example output
NAME                 STATUS   ROLES           AGE   VERSION
kind-control-plane   Ready    control-plane   45s   v1.30.x

A 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.

Pods across all namespaces
kubectl get pods -A
Example output
NAMESPACE            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          1m

The 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.

Cluster info
kubectl cluster-info
Example output
Kubernetes 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:

SectionWhat it is
clustersWhat clusters exist (API server URL, CA cert)
usersWhat credentials to use against each cluster (cert, token, etc.)
contextsA 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.

List contexts
kubectl config get-contexts
Example output
CURRENT   NAME             CLUSTER          AUTHINFO         NAMESPACE
*         kind-kind        kind-kind        kind-kind        
Current context
kubectl config current-context
Example output
kind-kind

Once 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.

Switch context
kubectl config use-context kind-kind

When 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:

  1. Open Docker Desktop → Settings (or Preferences)
  2. Click the Kubernetes tab in the left menu
  3. 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.

Switch to Docker Desktop k8s
kubectl config use-context docker-desktop
kubectl get nodes
Example output
NAME             STATUS   ROLES           AGE   VERSION
docker-desktop   Ready    control-plane   2m    v1.30.x

Every 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.

Start a minikube cluster
minikube start

To pick a driver explicitly, use --driver. The default is auto-selected based on the environment — docker / hyperkit / kvm, etc.

Start with explicit driver
minikube start --driver=docker

When minikube comes up, the context switches to minikube.

Verify
kubectl config use-context minikube
kubectl get nodes

minikube’s selling point — addons — is managed with minikube addons. To turn on the ingress controller, for example:

Enable the ingress addon
minikube addons enable ingress

Cleaning up #

Knowing how to clean up is useful after a learning session, or whenever you want a fresh start.

kind

Delete the kind cluster
kind delete cluster

If 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

Stop / delete minikube
minikube stop      # pause (you can start again later)
minikube delete    # full delete

A 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 with kubectl version --client.
  • kind uses one Docker container as a node. kind create cluster brings up a single-node cluster, and you can see the kindest/node container in docker ps.
  • kubectl get nodes / kubectl get pods -A / kubectl cluster-info show the components mentioned in #1 actually running — kube-apiserver, etcd, coredns, and so on.
  • kubeconfig (~/.kube/config) has three sections — clusters / users / contexts — and kubectl config use-context is 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.

X