There is no single "install Kubernetes" step. There are several distributions aimed at different purposes, and picking the wrong one wastes a lot of time.
For learning, the answer is almost always kind or minikube. For production, it is almost always a managed cloud cluster.
| Option | Good for | Runs where | Multi-node |
|---|---|---|---|
| kind | Learning, CI, testing | Docker containers | Yes, easily |
| minikube | Learning, add-ons, Ingress | VM or Docker | Yes |
| Docker Desktop | Quick single-node use | Its own VM | No |
| k3s | Edge, IoT, small servers | A real host | Yes |
| kubeadm | Learning the internals, self-managed production | Real machines | Yes |
| EKS / GKE / AKS | Production | Cloud | Yes |
Runs each Kubernetes node as a Docker container. It is the fastest to start and stop, which makes it the better choice for CI and for repeatedly throwing clusters away.
brew install kind # or: go install sigs.k8s.io/kind@latest
kind create cluster --name dev
kubectl get nodesNAME STATUS ROLES AGE VERSION
dev-control-plane Ready control-plane 45s v1.31.2
A multi-node cluster is a short config file, which matters because single-node clusters hide every scheduling and affinity problem you will eventually meet:
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: workerkind create cluster --name dev --config kind-config.yamlThe main friction is that images must be loaded into the cluster explicitly, since it has no access to your local Docker images:
kind load docker-image myapp:dev --name devOlder, more batteries-included. Its add-on system is the reason to prefer it. Ingress, metrics-server and a dashboard are one command each rather than a Helm install.
brew install minikube
minikube start --cpus=4 --memory=8192
minikube addons enable ingress
minikube addons enable metrics-serverminikube service payments-api --url # reach a NodePort Service
minikube tunnel # make LoadBalancer Services work
minikube dashboardminikube tunnel is genuinely useful: it satisfies LoadBalancer Services, which otherwise sit at <pending> forever on any local cluster.
kind if you want speed, multi-node, or CI. minikube if you want Ingress and metrics-server working with minimal setup.
Both are fine. Do not spend long deciding.
Kubernetes is a checkbox in its settings, which makes it the lowest-effort option if you already run Docker Desktop.
The limitations are real: single node only, a Kubernetes version tied to the Docker Desktop release, and it competes with your containers for the same VM resources. Fine for a quick look, limiting past that.
A certified Kubernetes distribution in a single binary under 100MB, with etcd swapped for SQLite by default and legacy cloud providers stripped out.
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodesGenuinely production-grade for edge deployments, IoT, and small servers. It is also a pleasant way to run a real cluster on a spare machine or a cheap VPS, and it includes Traefik and a load balancer implementation out of the box.
The official tool for building a cluster on real machines. You install a container runtime, run kubeadm init, install a CNI plugin, and join workers.
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml
kubeadm token create --print-join-commandWorth doing once. Installing the CNI by hand and watching CoreDNS go from Pending to Running teaches more about how clusters fit together than any amount of reading.
Worth being honest about as a production choice: you own etcd backups, certificate rotation, control plane upgrades and node patching. That is a real ongoing job.
For production, use EKS, GKE or AKS unless you have a specific reason not to. The provider runs the control plane, backs up etcd, and handles its upgrades.
# AWS
eksctl create cluster --name production --region us-east-1 --nodes 3
# GCP
gcloud container clusters create-auto production --region us-central1
# Azure
az aks create --resource-group rg --name production --node-count 3The control plane fee is small next to the cost of someone maintaining one. Note that you still own the worker nodes, the networking, add-on upgrades and access control. Managed means the control plane, not the whole cluster.
Kubernetes is not light. A local cluster wants at least 2 CPUs and 4GB of RAM free, and 4 CPUs with 8GB makes a real difference once you install an ingress controller and a monitoring stack.
If Pods sit Pending with Insufficient cpu on a fresh local cluster, the cluster is too small rather than misconfigured:
minikube start --cpus=4 --memory=8192kubectl cluster-info
kubectl get nodes
kubectl get pods -A # control plane components should be RunningAll kube-system Pods Running means the cluster is healthy. CoreDNS stuck Pending on a kubeadm cluster means the CNI plugin has not been installed.
# Prove it works end to end
kubectl create deployment web --image=nginx:1.27
kubectl expose deployment web --port=80
kubectl port-forward service/web 8080:80| Sub-topic | What you will learn |
|---|---|
| Installing kubectl | Install and configure the CLI |
| minikube | Set up minikube and its add-ons |
| kind | Multi-node clusters in Docker |
| Task | Command |
|---|---|
| kind cluster | kind create cluster --name dev |
| kind multi-node | kind create cluster --config kind-config.yaml |
| Load an image into kind | kind load docker-image <img> --name dev |
| minikube cluster | minikube start --cpus=4 --memory=8192 |
| minikube add-on | minikube addons enable ingress |
| Check health | kubectl get nodes && kubectl get pods -A |
| Cluster endpoint | kubectl cluster-info |
| Delete | kind delete cluster --name dev / minikube delete |
kind load docker-image: it cannot see your local Docker images.minikube tunnel is what makes LoadBalancer Services work locally.Insufficient cpu on a fresh cluster usually means it is too small.<MCQExercise exercise={{ id: 'k8s-install-kind-image', title: 'ImagePullBackOff on a local image', description: 'You build myapp:dev locally and deploy it to a kind cluster. The Pod reports ImagePullBackOff even though docker images lists it. Why?', options: [ { id: 'a', text: 'kind nodes are separate Docker containers with their own image store. The image must be loaded with kind load docker-image' }, { id: 'b', text: 'kind requires all images to come from a remote registry and cannot run local builds' }, { id: 'c', text: 'The image needs imagePullPolicy: Always to be found locally' }, { id: 'd', text: 'Image tags other than latest are not supported by kind' }, ], correctId: 'a', explanation: 'Each kind node runs as a Docker container with its own containerd instance, so your host's Docker image store is invisible to it. kind load docker-image myapp:dev --name dev copies the image into every node. Setting imagePullPolicy: IfNotPresent alongside is sensible, since Always would make the kubelet try the registry anyway.', wrongExplanations: { b: 'Local images work fine once loaded. kind load exists precisely to support this workflow without pushing to a registry.', c: 'Always forces a registry pull, which is the opposite of what is needed. IfNotPresent lets the loaded image be used.', d: 'Any tag works. The problem is where the image lives, not what it is called.', }, }} />
<MCQExercise exercise={{ id: 'k8s-install-lb-pending', title: 'A pending LoadBalancer locally', description: 'A LoadBalancer Service on minikube shows EXTERNAL-IP as pending indefinitely. What is the explanation?', options: [ { id: 'a', text: 'There is no cloud provider to create a load balancer. Minikube tunnel simulates one, or use port-forward' }, { id: 'b', text: 'The Service selector matches no Pods' }, { id: 'c', text: 'minikube only supports ClusterIP Services' }, { id: 'd', text: 'The ingress add-on must be enabled before any Service can get an address' }, ], correctId: 'a', explanation: 'EXTERNAL-IP is populated by a cloud controller that provisions a real load balancer, and a local cluster has none, so the field stays pending forever. minikube tunnel creates a route that satisfies LoadBalancer Services, and on kind the equivalent is installing MetalLB. For quick access, kubectl port-forward avoids the question entirely.', wrongExplanations: { b: 'Address assignment and endpoint population are independent. A Service with no matching Pods still receives an address on a real cloud provider.', c: 'minikube supports every Service type. LoadBalancer simply needs something to fulfil the request, which is what tunnel provides.', d: 'The ingress add-on installs an Ingress controller, which is a different mechanism entirely and does not assign Service external IPs.', }, }} />
kind creates nodes as Docker containers, which makes it very fast and well suited to CI pipelines, and multi-node clusters are a few lines of config. minikube typically runs in a VM or Docker and provides curated add-ons, so getting Ingress or metrics-server working takes one command rather than a Helm install. kind requires kind load docker-image to make local builds available; minikube offers minikube tunnel to make LoadBalancer Services function. Both are fine. Choose on whether you value speed or bundled add-ons.
It is a reasonable low-effort option if you already run Docker Desktop, since enabling it is a checkbox. The limitations are that it is single-node, its Kubernetes version is tied to the Docker Desktop release, and it shares resources with your containers. For anything beyond a quick look. Particularly anything involving scheduling, node affinity or multi-node behaviour. Kind or minikube will serve you better.
Yes. kubeadm builds a conformant cluster on your own machines, and k3s is a certified lightweight distribution that runs well on small servers and edge hardware. Be clear about what you take on: etcd backups, certificate rotation, control plane upgrades, node patching, and the networking and storage integrations a cloud provider supplies. For most teams the managed control plane fee is smaller than the engineering time it replaces.
At least 2 CPUs and 4GB of RAM free, and 4 CPUs with 8GB is a much better experience once you add an ingress controller or a monitoring stack. Kubernetes control plane components alone consume a meaningful share of a small allocation. If Pods sit Pending with Insufficient cpu on a freshly created cluster, the cluster is undersized rather than misconfigured. Restart it with more resources.
No CNI plugin is installed. kubeadm init deliberately leaves the choice of network plugin to you, and without one Pods cannot be assigned addresses, so CoreDNS cannot start. Apply a CNI such as Calico, Flannel or Cilium and CoreDNS moves to Running within a minute. If you passed --pod-network-cidr to kubeadm init, make sure the plugin's configuration uses the same range.
Learn when to use a Kubernetes ConfigMap vs a Secret, how to consume them as env vars or files, why base64 is not encryption, and where real secrets belong.
Create a Kubernetes ConfigMap with kubectl or YAML, use it as env vars with envFrom or as mounted files, and find out why a ConfigMap change did not apply.
Create Kubernetes Secrets, mount them as env vars or files, use imagePullSecrets and TLS secrets, and learn why base64 is not encryption.