KubeConfig

Clusters, users, and contexts explained.

Beginner8 min · lesson 45 of 65
In plain terms
A kubeconfig is your keyring: which building (cluster), which key (your identity), and which floor you start on (namespace). Switching clusters is just reaching for a different key.

Every kubectl command begins by reading a small text file on your own machine. Not the cluster, not etcd (the cluster's key-value database). A local file, usually ~/.kube/config, that decides three things before any traffic leaves your laptop: which cluster to reach, which credential to send, and which namespace to assume. Point it at the wrong place and kubectl won't warn you. It runs your command, correctly, against a cluster you never meant to touch.

Think of a keyring for a company with buildings all over town. Each building is a cluster, meaning one API server (the front door every request has to pass through) plus the certificate authority that proves that door is genuine. Each key is a user, meaning a credential that proves who you are. A tag on the key says which floor to start on, which is your default namespace. A context is one key, on one building, with one floor tag. The reason a kubeconfig exists is so a single file can hold your keys to dev, staging, and prod at once, and current-context is the key you happen to be holding right now.

The three lists, and current-context

Open the file (it's written in YAML, a plain-text format for settings) and you find exactly three lists. clusters gives each API server's address and its CA data, so kubectl knows where to connect and how to check the certificate the server hands back over TLS, the encryption behind every https:// address. users gives credentials: a client certificate, a bearer token, or an exec block that runs a command to fetch one. contexts is the glue, and each context names one cluster, one user, and an optional namespace. None of this talks to the cluster by itself. The file is pure client-side configuration. When you run a command, kubectl reads current-context, looks up that context's cluster and user, and builds an HTTPS client out of those parts. The API server never sees your kubeconfig. It only sees the credential kubectl pulled from it, and then its own authentication and RBAC (Role-Based Access Control) decide what you're allowed to do.

terminal
kubectl config get-contexts
output
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* prod-admin prod-cluster kubernetes-admin default
staging staging ci-deployer payments
dev dev-cluster dev-admin default

Two habits keep this safe. Put the context and namespace in your shell prompt (kube-ps1 and starship both do it) so the answer to 'where am I?' is always on screen. And when a command looks scary, inspect before you fire: kubectl config view --minify prints only the current context's cluster and user, fully resolved, so you can read the exact server URL you're about to hit. The wrong current-context is the most common way a careful engineer runs a good command against a bad cluster.

terminal
kubectl config current-context
kubectl config use-context staging
kubectl config set-context --current --namespace=web
output
prod-admin
Switched to context "staging".
Context "staging" modified.
How a kubeconfig is put together
clusters: where to connect, and how to trust it
server: https://api.prod:6443
the API server address kubectl dials
certificate-authority-data
the CA that must have signed the server cert, or the TLS handshake fails
users: the credential that proves who you are
client-certificate / token
a static credential baked into the file
exec
a helper command that mints a short-lived token on demand (cloud clusters)
contexts: the pairing kubectl actually uses
context = cluster + user + namespace
a single named connection
current-context
the one context every command runs against until you switch
current-context is the only thing standing between your command and the wrong cluster.

Build a scoped kubeconfig, not another admin file

The admin.conf file kubeadm writes when it builds a cluster is a full cluster-admin credential. It can run any action against any object in the cluster. (On current versions that power comes from a group named kubeadm:cluster-admins; kubeadm also drops a separate super-admin.conf, the real break-glass file that bypasses permission checks entirely.) Copying the admin file around is like handing out the master key to every building so someone can water one plant. The better move is to mint a narrow credential and wrap it in its own kubeconfig. A ServiceAccount (a non-human identity that workloads and automation use) is perfect for this. Since v1.24 you don't dig a static token out of a Secret. You ask the API server for a short-lived one with kubectl create token, and it returns a signed JWT (JSON Web Token) that expires on the schedule you set. Bind that account to a tightly-scoped Role and the holder can do only what you granted.

terminal
kubectl create serviceaccount deployer -n payments
TOKEN=$(kubectl create token deployer -n payments --duration=24h)
output
serviceaccount/deployer created
# $TOKEN now holds a signed, 24h JWT: eyJhbGciOiJSUzI1NiIsImtpZCI6IjhZ...Qssw5c
terminal
kubectl config set-cluster prod --server=https://api.prod:6443 \
--certificate-authority=ca.crt --embed-certs --kubeconfig=deployer.kubeconfig
kubectl config set-credentials deployer --token="$TOKEN" --kubeconfig=deployer.kubeconfig
kubectl config set-context deployer --cluster=prod --user=deployer \
--namespace=payments --kubeconfig=deployer.kubeconfig
output
Cluster "prod" set.
User "deployer" set.
Context "deployer" created.

Now prove the credential does what you think and nothing more. kubectl auth whoami (v1.26 and up) echoes back the identity the server sees, and kubectl auth can-i asks the authorization layer directly, so you don't have to run a destructive command just to learn whether you could. Here the account can delete pods (Kubernetes' smallest running unit) in payments, where its Role lives, and nowhere else.

terminal
export KUBECONFIG=deployer.kubeconfig
kubectl auth whoami
kubectl auth can-i delete pods -n payments
kubectl auth can-i delete pods -n kube-system
output
ATTRIBUTE VALUE
Username system:serviceaccount:payments:deployer
Groups [system:serviceaccounts system:serviceaccounts:payments system:authenticated]
yes
no

Cloud clusters don't hand you a token

On EKS, GKE, and AKS (the managed Kubernetes services from AWS, Google, and Azure), the users section often has no token or certificate at all. It has an exec block. Think of a locksmith who cuts you a fresh key each morning that stops working by nightfall. Instead of storing a long-lived credential, the kubeconfig stores a command. Every time kubectl needs to authenticate, it runs that helper (aws eks get-token, gke-gcloud-auth-plugin, kubelogin), the helper returns a token that's valid for a few minutes, and kubectl sends it. Nothing sensitive sits in the file, so a stolen cloud kubeconfig is close to useless without your cloud login behind it.

~/.kube/config
users:
- name: prod-eks
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: aws
args:
- eks
- get-token
- --cluster-name
- prod
- --region
- us-east-1
interactiveMode: IfAvailable
output
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-1-42.ec2.internal Ready <none> 9d v1.31.4
ip-10-0-2-17.ec2.internal Ready <none> 9d v1.31.4

How kubectl finds the file, and where writes go

kubectl looks for its config in a fixed order. A --kubeconfig flag wins if you pass one. Otherwise the KUBECONFIG environment variable, which can list several files separated by colons on Linux and macOS (semicolons on Windows). If neither is set, it falls back to ~/.kube/config. When KUBECONFIG lists more than one file, kubectl merges them into a single view, matches entries by name, and lets the first file win any collision. One more sharp edge lives here: when you change something with kubectl config, such as use-context, the write lands in the first file in that list, even when the thing you changed is defined in a different file. That is how a context switch gets saved somewhere you didn't expect.

When a new kubeconfig won't connect, the error usually names the layer that broke. 'Unable to connect to the server: x509: certificate signed by unknown authority' means the cluster's CA data is wrong or missing, so TLS failed before authentication even started. 'error: You must be logged in to the server (Unauthorized)' means the connection was fine but the credential was rejected, an expired token or the wrong user. And 'error: invalid configuration: context was not found for specified context: dev' means the file points at a current-context that isn't among the contexts it can see. Read which one you got and you know which of the three lists to go fix.

Merged configs collide by name, silently
KUBECONFIG can point at several files at once, and kubectl merges them by name, first file wins. kubeadm names its cluster, user, and context almost identically on every cluster it builds (kubernetes-admin@kubernetes and friends), so if you drop two kubeadm configs into the same KUBECONFIG, the second cluster's endpoint and CA get shadowed by the first. kubectl then shows you a context that looks right but quietly points at the wrong API server. Rename cluster, user, and context on import, or keep one file per cluster and pick with --kubeconfig, and the trap disappears.

Prefer short-lived exec plugins or OIDC over long-lived embedded tokens on laptops.

File permissions on kubeconfig should be tight. World-readable configs leak cluster access.

CI should use narrowly scoped ServiceAccount tokens, not a human admin kubeconfig checked into a repo.

Try this

Print current context, list contexts, and switch to another if you have one. Intentionally point at the wrong namespace and watch a get miss your objects.

terminal
$ kubectl config get-contexts
$ kubectl config current-context
$ kubectl config use-context staging
$ kubectl config set-context --current --namespace=web
$ kubectl create serviceaccount deployer -n payments
TOKEN=$(kubectl create token deployer -n payments --duration=24h)
$ kubectl config set-cluster prod --server=https://api.prod:6443 \
--certificate-authority=ca.crt --embed-certs --kubeconfig=deployer.kubeconfig
$ kubectl config set-credentials deployer --token="$TOKEN" --kubeconfig=deployer.kubeconfig
$ kubectl config set-context deployer --cluster=prod --user=deployer \
--namespace=payments --kubeconfig=deployer.kubeconfig
$ export KUBECONFIG=deployer.kubeconfig
$ kubectl auth whoami
$ kubectl auth can-i delete pods -n payments
$ kubectl auth can-i delete pods -n kube-system
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-1-42.ec2.internal Ready <none> 9d v1.31.4
ip-10-0-2-17.ec2.internal Ready <none> 9d v1.31.4

Takeaway

A kubeconfig ties cluster, user, and context. Wrong context is an operational hazard equal to wrong credentials.

Quick check
01Your KUBECONFIG lists two files, prod first then dev (KUBECONFIG=~/.kube/prod:~/.kube/dev). You run kubectl config use-context dev and it switches cleanly. Later a teammate opens ~/.kube/prod on its own and kubectl errors: invalid configuration: context was not found for specified context: dev. What happened?
Correct — Config writes land in the first file in KUBECONFIG regardless of where the entry lives, so prod's file now names a context it doesn't contain.
Incorrect — Merging is a read-time, in-memory operation; it never rewrites the source files.
Incorrect — When KUBECONFIG is set, the default ~/.kube/config is ignored entirely and never loaded or written.
Incorrect — An expired token gives an Unauthorized error, not a 'context was not found' error; those failures live at different layers.
02On an EKS or GKE cluster the users section of your kubeconfig holds an exec block instead of a token or client certificate. Why does that make a stolen copy of the file far less dangerous?
Incorrect — nothing is encrypted; the file simply stores no credential at all, only a command.
Incorrect — there is no such origin check; the real protection is that no secret sits in the file.
Correct — the helper (aws eks get-token and the like) fetches a few-minute token each call, so a stolen file is near-useless without your cloud identity behind it.
Incorrect — the command runs anywhere the helper is installed; the safety comes from storing no token, not from machine-binding.
03You import a new kubeconfig and kubectl fails with: error: You must be logged in to the server (Unauthorized). A kubeconfig has three lists - clusters, users, and contexts. Which should you go fix?
Incorrect — bad CA data gives an x509 unknown-authority error before authentication; this message came after the connection succeeded.
Correct — Unauthorized means TLS succeeded and the credential itself was refused, and credentials live in the users list.
Incorrect — a missing context produces a 'context was not found' error, not Unauthorized.
Incorrect — an unreachable server gives a connection error, not an authentication rejection; here the handshake clearly completed.

Related