KubeConfig
Clusters, users, and contexts explained.
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.
kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE* prod-admin prod-cluster kubernetes-admin defaultstaging staging ci-deployer paymentsdev 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.
kubectl config current-contextkubectl config use-context stagingkubectl config set-context --current --namespace=web
prod-adminSwitched to context "staging".Context "staging" modified.
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.
kubectl create serviceaccount deployer -n paymentsTOKEN=$(kubectl create token deployer -n payments --duration=24h)
serviceaccount/deployer created# $TOKEN now holds a signed, 24h JWT: eyJhbGciOiJSUzI1NiIsImtpZCI6IjhZ...Qssw5c
kubectl config set-cluster prod --server=https://api.prod:6443 \--certificate-authority=ca.crt --embed-certs --kubeconfig=deployer.kubeconfigkubectl config set-credentials deployer --token="$TOKEN" --kubeconfig=deployer.kubeconfigkubectl config set-context deployer --cluster=prod --user=deployer \--namespace=payments --kubeconfig=deployer.kubeconfig
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.
export KUBECONFIG=deployer.kubeconfigkubectl auth whoamikubectl auth can-i delete pods -n paymentskubectl auth can-i delete pods -n kube-system
ATTRIBUTE VALUEUsername system:serviceaccount:payments:deployerGroups [system:serviceaccounts system:serviceaccounts:payments system:authenticated]yesno
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.
users:- name: prod-eksuser:exec:apiVersion: client.authentication.k8s.io/v1beta1command: awsargs:- eks- get-token- --cluster-name- prod- --region- us-east-1interactiveMode: IfAvailable
$ kubectl get nodesNAME STATUS ROLES AGE VERSIONip-10-0-1-42.ec2.internal Ready <none> 9d v1.31.4ip-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.
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.
$ 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 paymentsTOKEN=$(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 nodesNAME STATUS ROLES AGE VERSIONip-10-0-1-42.ec2.internal Ready <none> 9d v1.31.4ip-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.