Sources: kubeconfig docs, kubectx
Working with one cluster is easy. Working with multiple clusters is where kubectl starts to hurt — and where the right kubeconfig discipline pays off.
What is a context?
A kubeconfig is a YAML file that defines clusters, users, and contexts:
apiVersion: v1
kind: Config
current-context: prod-eks-admin # ← which one is "active"
clusters:
- name: prod-eks
cluster:
server: https://api.prod.eks.example.com
certificate-authority-data: <base64>
- name: staging-eks
cluster:
server: https://api.staging.eks.example.com
certificate-authority-data: <base64>
- name: dev-minikube
cluster:
server: https://192.168.49.2:8443
insecure-skip-tls-verify: true
users:
- name: prod-admin
user:
exec: # pluggable auth (aws-iam-authenticator, etc.)
apiVersion: client.authentication.k8s.io/v1
command: aws
args: [eks, get-token, --cluster-name, prod]
- name: dev-local
user:
client-certificate-data: <base64>
client-key-data: <base64>
contexts:
- name: prod-eks-admin
context:
cluster: prod-eks
user: prod-admin
namespace: web # default namespace for this context
- name: staging-eks-admin
context:
cluster: staging-eks
user: prod-admin # same user, different cluster
namespace: web
- name: dev-minikube
context:
cluster: dev-minikube
user: dev-local
namespace: defaultThree layers:
- Cluster — where the apiserver is, and how to verify its identity
- User — who you are, and how to prove it
- Context — cluster + user + default namespace, named so you can switch
A kubectl invocation picks one context, sends requests to that cluster’s apiserver, and authenticates as that user.
Where the kubeconfig lives
$KUBECONFIG # env var (colon-separated, merges multiple)
~/.kube/config # default
When $KUBECONFIG is set, it overrides ~/.kube/config. You can also merge multiple files:
export KUBECONFIG=~/.kube/config:~/.kube/eks-prod:~/.kube/gke-stagingThis is how you keep cluster-specific kubeconfigs separate and merge on demand.
Essential commands
# view
kubectl config view # human-readable
kubectl config view --raw # show certs (debug only, never commit)
kubectl config get-contexts # list contexts
kubectl config current-context # show active
# switch
kubectl config use-context prod-eks
# set default namespace for current context
kubectl config set-context --current --namespace=web
# rename / delete
kubectl config rename-context old-name new-name
kubectl config delete-context old-name
# add cluster / user / context manually
kubectl config set-cluster my-cluster --server=https://...
kubectl config set-credentials my-user --token=...
kubectl config set-context my-context --cluster=my-cluster --user=my-userTooling: kubectx + kubens
Plain kubectl config use-context works but is verbose. kubectx gives you fuzzy search and a faster switch:
# install (krew)
kubectl krew install ctx ns
# switch context (fzf-style picker if multiple match)
kubectx prod
kubectx -
# interactively pick
kubectx
# switch namespace (same UX)
kubens web
kubens -
# merge two kubeconfigs
KUBECONFIG=~/.kube/config:~/.kube/new kubectx
# rename context
kubectx new-name=old-namekubectx is universally the right tool once you have more than 2-3 contexts.
Patterns for multi-cluster work
Pattern 1: one kubeconfig per cluster, merge via KUBECONFIG
~/.kube/config # your local dev minikube
~/.kube/eks-prod # generated by `aws eks update-kubeconfig`
~/.kube/gke-staging # generated by `gcloud container ...`
# shell alias to merge on demand
export KUBECONFIG=~/.kube/config:~/.kube/eks-prod:~/.kube/gke-stagingPros: clean separation. Cons: you have to remember to export.
Pattern 2: directory of kubeconfigs, fzf pick
~/.kube/configs/
├── prod-eks.yaml
├── staging-eks.yaml
├── dev-minikube.yaml
├── mgmt-eks.yaml
└── ...
# in .zshrc / .bashrc
kc() {
local fzf_args="--height 40% --layout=reverse"
local chosen=$(ls ~/.kube/configs/ | fzf $fzf_args)
[[ -n "$chosen" ]] && export KUBECONFIG=~/.kube/configs/$chosen
kubectl config get-contexts
}Pros: each cluster is a single file. Cons: only one cluster active at a time.
Pattern 3: per-project .kube/config
Many teams put a kubeconfig in the project’s repo (or in a sealed secret in the repo). Helm/Argo CD pick it up via env or by being run from the project dir.
# .envrc in the project
export KUBECONFIG=$PWD/.kubeconfig
direnv allowdirenv is the tool that auto-exports on cd. Combines well with this pattern.
Pattern 4: cluster registry / fleet
If you have 50+ clusters, you don’t kubectl into each one — you operate them through a fleet tool. See multi-cluster for the fleet-management patterns.
Common gotchas
- Context name is just a label — it doesn’t have to match the cluster name. Pick names that say what + where (
prod-eks-admin>prod). - Merging kubeconfigs is positional and appends. If the same context name exists in two files, the last one wins.
- Certs expire. A kubeconfig that worked yesterday might fail today. Symptom:
Unable to connect to the server: x509: certificate has expired or is not yet valid. Re-runaws eks update-kubeconfigor whichever tool generated it. - Tokens expire fast. EKS/GKE/AKS all use short-lived tokens (15min-12hr). Your kubeconfig has an
execblock that re-fetches them on each call. If thatexecis slow, yourkubectlcalls are slow. kubectl config viewredacts secrets by default — you see<set to the value of the ...>instead of the actual cert/token. Use--raw(carefully).- The
current-contextis a single pointer. You can’t have two contexts “active” at once. If you need to copy a secret from cluster A to cluster B, that’s twokubectlcalls with different kubeconfigs — you can’t pipe them unless you doKUBECONFIG=...per call. - Don’t commit kubeconfigs to git — they contain credentials. The
~/.kube/configpattern is fine, but if you have a shared cluster config, use a secrets manager (Vault, sealed-secrets) anddirenvto inject. - Plugins like
aws-iam-authenticatorneed to be on$PATHof whatever shell runskubectl. Watch out for this in CI —kubectlwon’t fail until you actually use it. kubectlcaches — but only the auth response, not the cluster state. State is always fresh.
Per-context default namespace
A frequently-missed feature: each context can have a default namespace, which is what kubectl uses when you don’t pass -n:
# set per-context
kubectl config set-context --current --namespace=web
# unset (back to "default")
kubectl config set-context --current --namespace=defaultWhy this matters:
- Most production traffic is in a specific namespace (not
default). Setting it per-context saves typing and prevents accidents. defaultis dangerous — many tools deploy there by default, and you don’t want yourkubectl deleteto accidentally hit cluster-wide resources.- For SREs doing incident response: set a context with the incident namespace baked in.
Security: keeping kubeconfigs safe
A kubeconfig is essentially a credential file. Treat it accordingly.
- File mode
600:chmod 600 ~/.kube/config - Never commit to git. Add
*.kubeconfigto.gitignore. Don’t even put it in a private repo — that doesn’t count as access control. - Use short-lived credentials. EKS/IRSA, GKE Workload Identity, AAD Pod Identity — all give you 15min-12hr tokens, not permanent certs.
- Audit periodically:
kubectl auth can-i --list -n kube-system --as=<your-user>— see what you can do. - Don’t run
kubectl proxyon a shared host — it exposes the apiserver onlocalhost:8001. If the host is reachable, the apiserver is reachable. - Use distinct contexts per cluster, not per user. Cluster admin in prod ≠ cluster admin in dev. The context name should encode where; the user/role should encode who.
Multi-context commands
If you genuinely need to run the same command against many clusters, scripting is the answer:
# shell loop
for ctx in $(kubectl config get-contexts -o name); do
echo "=== $ctx ==="
kubectl --context $ctx get nodes
done
# xargs
kubectl config get-contexts -o name | \
xargs -I {} kubectl --context {} get nodesFor serious fleet work, use kubefwd, fleet, or armada — but the shell loop covers most ad-hoc needs.
Quick troubleshooting
# which context am I on?
kubectl config current-context
# can I even reach the apiserver?
kubectl cluster-info
# is my auth working?
kubectl auth whoami # 1.28+
kubectl auth can-i get pods
# is the server cert valid?
kubectl config view --raw | grep certificate-authority-data | base64 -d | openssl x509 -text
# what's the active kubeconfig file actually?
echo $KUBECONFIG
ls -la ~/.kube/configIf cluster-info returns connection refused or a TLS error, the issue is network or kubeconfig — not your RBAC. If it works but auth can-i denies, it’s RBAC.
See also
- kubectl — the CLI
- k9s — context switching inside a TUI
- multi-cluster — fleet management at scale
- kubectx — fast context switcher
- kubeconfig reference