kubectl and kubeconfig: connecting to the cluster
How to install the right kubectl version, where the connection config lives, and how to avoid running a training command against prod.
kubectl is a client for the Kubernetes API. It does exactly one thing: build an HTTP request to the api-server, send it, and show the answer. It doesn't create or bring up a cluster.
The difference shows up right away: kind create cluster creates a
cluster, kubectl get nodes only asks an already existing cluster what
nodes it has. If there's no cluster, kubectl will honestly say it
couldn't connect.
So the first skill you need after installing kubectl is knowing where it's pointing right now. A training command that lands on a work cluster breaks someone else's service, not your sandbox.
Concepts
- kubectl — a command-line client for the Kubernetes API.
- kubeconfig — a file with cluster addresses, credentials, and
contexts. By default
~/.kube/config. - Context — a named bundle of "cluster + user + default namespace". Exactly one is active at any time.
- Version skew — the allowed gap between the minor versions of kubectl and the api-server, covered in detail in the previous lesson.
Some tools bring their own client along. minikube can do
minikube kubectl -- get po -A, MicroK8s is invoked as
microk8s kubectl get nodes, k0s as sudo k0s kubectl get nodes. A
separate kubectl is still more convenient: it's one binary for every
cluster, and it doesn't depend on which tool you used to bring up today's
sandbox.
Installation
macOS:
brew install kubectl
Or download the binary directly (for an Intel Mac, replace arm64 with
amd64):
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
Linux, binary with a checksum check. The second curl downloads the
sha256 file next to the binary, sha256sum --check verifies it:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
Don't skip the checksum check. The binary goes into a system path and runs with permissions that give access to the cluster. Verifying the file's integrity is cheaper than debugging later.
Linux via apt. Note the v1.37 in both URLs: the repository is split by
minor branch, and for a different version you edit the line by hand.
The /etc/apt/keyrings directory exists by default on Debian 12 and
Ubuntu 22.04; on older systems the first command creates it.
sudo mkdir -p -m 755 /etc/apt/keyrings
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.37/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update && sudo apt-get install -y kubectl
Windows, any of the three package managers:
winget install -e --id Kubernetes.kubectl
choco install kubernetes-cli
scoop install kubectl
On WSL2, install kubectl inside the Linux distro, the same way as on Linux.
Check after installing:
kubectl version --client
Client Version: v1.36.1
Kustomize Version: v5.8.1
Which version to install
The latest stable Kubernetes version as of October 9, 2026 is v1.37.1. Branch 1.37 came out on 2026-08-26. You can always ask the project itself for the current version:
curl -L https://dl.k8s.io/release/stable.txt
Supported branches:
| Version | Patch | EOL |
|---|---|---|
| 1.37 | v1.37.1 | 2027-10-28 |
| 1.36 | v1.36.5 | 2027-06-28 |
| 1.35 | v1.35.9 | 2027-02-28 |
Branch 1.34 is in maintenance mode until 2026-10-27, its last patch is v1.34.12. The patch window is about 12 months per branch.
The client can't drift far from the cluster. The version skew policy lets kubectl differ from the api-server by no more than one minor version in either direction: with a 1.37 cluster, clients 1.36, 1.37, and 1.38 all work. Outside that window, behavior isn't guaranteed. The client builds requests against the API schema it knows, and over a few releases the cluster adds and removes fields.
The problem gets real once there's more than one cluster. A local kind gives you 1.37, a work cluster might be on 1.35. A 1.36 client falls inside both windows. A 1.38 client no longer fits 1.35's window.
Two commands every check starts with:
kubectl version # client version and cluster version
kubectl cluster-info # control plane address of the current context
kubectl version shows both versions at once, so a mismatch is visible
without arithmetic. kubectl cluster-info also answers the question "am
I even connected to the right cluster". If the cluster is unreachable,
both commands print the address they tried to reach:
Unable to connect to the server: dial tcp: lookup staging.k8s.invalid: no such host
kubectl took the address in that error from the active context. That's where troubleshooting starts.
kubeconfig
kubectl gets cluster addresses, certificates, and tokens from the
kubeconfig file. By default that's ~/.kube/config.
The $KUBECONFIG variable overrides the path. You do this, for
example, with k3s, which puts its config in a system directory:
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
The variable lives in the current terminal. In a new window, kubectl
reads ~/.kube/config again, until you export it once more. The
--kubeconfig <file> flag on a single command outranks the variable:
with it, kubectl reads exactly that file.
Inside kubeconfig sit three kinds of entries: clusters (address and CA),
users (a certificate or a token), and contexts. The active context is
recorded in the current-context field, and it's what decides where the
next command goes.
kubeconfig
├─ clusters apiserver address + CA
├─ users certificate or token
└─ contexts cluster + user + ns
Context names are usually assigned by the tool. kind gives you
kind-<name>, Docker Desktop gives you docker-desktop.
kubectl config get-contexts # list, star next to the active one
kubectl config current-context # just the active one's name
kubectl config use-context kind-lab
A context has a default namespace. If you don't set it, every command
will go to default, and you'll have to type -n demo by hand every
time. You can pin the namespace to the context once:
kubectl config set-context --current --namespace=demo
All kubectl config commands only edit the file on your machine.
Nothing changes in the cluster, and they never reach the server. kubectl
doesn't check whether that namespace exists: it will write a typo
silently.
Several files at once
You can put several files in $KUBECONFIG separated by a colon (a
semicolon on Windows), and kubectl will merge them into one picture:
export KUBECONFIG=~/.kube/config:~/.kube/lab.yaml
kubectl config get-contexts
Reading this way is convenient, but writing is dangerous. The merge rules are:
- on read, the first file that set a value wins. If
current-contextis in the first file, that one is active; - kubectl writes a changed value to the file where it's defined;
- kubectl creates a new entry (context, cluster, user) in the first file of the list.
The second rule sets a trap. You merged your work and training files and
ran use-context on the training context. current-context was defined
in the work file, so the new value got written there. The work file now
points to a context that doesn't exist in it.
Local tools edit your kubeconfig
Almost every local cluster, when created, adds itself to kubeconfig and switches the active context to itself. Usually that's convenient. Sometimes it backfires.
A live example: colima merges its context into the first file in the
$KUBECONFIG list
(issue #1627, open as
of October 2026). If the work config with a prod cluster is listed
first, colima will write the training context straight into it and make
it active too.
That gives a simple rule. Keep a training kubeconfig as a separate file and point to it explicitly, as a single path:
export KUBECONFIG=~/.kube/lab.yaml
kind can write a new cluster's config straight to a separate file:
kind create cluster --kubeconfig ~/.kube/lab.yaml. Prod simply isn't
reachable in that session, and a namespace typo can break nothing worse
than the sandbox. Before any command that changes something, the habit
is the same: kubectl config current-context first, everything else
after.
Tools for looking at the cluster
kubectl alone gets you far, but a couple of tools save time.
k9s v0.51.0 (2026-06-06) is a terminal UI for the cluster. Object
lists refresh themselves, logs and describe open with one keystroke.
Install with brew install derailed/k9s/k9s, scoop install k9s,
choco install k9s, or pacman -S k9s. It helps to set
TERM=xterm-256color and KUBE_EDITOR ahead of time.
kubectx and kubens v0.11.0 (2026-03-27) from the ahmetb/kubectx
repo cut down the busywork with contexts: kubectx switches the
context, kubens changes the namespace. Install with
brew install kubectx, sudo apt install kubectx, or as plugins with
kubectl krew install ctx ns.
Graphical dashboards are murkier than people tend to assume:
- Lens Desktop: closed source and freemium. The free Personal tier requires a Lens ID registration and a personal key.
- OpenLens is no longer maintained. Lens closed its source, and the OpenLens maintainer says right in the README that there won't be updates. The last release, v6.5.2, came out in June 2023. "Install OpenLens" advice from old articles is outdated.
- FreeLens v1.10.3 (2026-07-07): a living fork for Linux, Windows, and macOS, MIT licensed.
- Among the alternatives, Headlamp (a kubernetes-sigs project) and YAKD are still alive.
Common mistakes
- Opening a new terminal and forgetting
export KUBECONFIG. kubectl will silently fall back to~/.kube/configand its active context. - Running
use-contextwhile several files are merged. The newcurrent-contextgoes into whichever file the field was defined in, usually the work one. - Mistaking
No resources found in default namespace.for an empty cluster. kubectl only looked in the current context's namespace.kubectl get pods -Awill show everything. - Fixing the network when the context is to blame. A connection
error prints an address. If it's not the one you expected, check
kubectl config get-contexts. - Installing the "freshest" kubectl for an old cluster. The skew window is ±1 minor version, no guarantees past that.
What you'll need for the tasks
- Lab 1 has no cluster: just kubectl and two kubeconfig files the lab
environment's script creates. You'll use
export KUBECONFIG,kubectl config get-contexts,use-context, andset-context --current. - Lab 2 brings up a kind cluster on your laptop. You need Docker,
kind,
and kubectl 1.34-1.36. The commands
kubectl cluster-infoandkubectl get pods -Aare added on top.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson k8s-local-02-kubectl
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress