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: default

Three 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-staging

This 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-user

Tooling: 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-name

kubectx 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-staging

Pros: 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 allow

direnv 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-run aws eks update-kubeconfig or whichever tool generated it.
  • Tokens expire fast. EKS/GKE/AKS all use short-lived tokens (15min-12hr). Your kubeconfig has an exec block that re-fetches them on each call. If that exec is slow, your kubectl calls are slow.
  • kubectl config view redacts secrets by default — you see <set to the value of the ...> instead of the actual cert/token. Use --raw (carefully).
  • The current-context is 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 two kubectl calls with different kubeconfigs — you can’t pipe them unless you do KUBECONFIG=... per call.
  • Don’t commit kubeconfigs to git — they contain credentials. The ~/.kube/config pattern is fine, but if you have a shared cluster config, use a secrets manager (Vault, sealed-secrets) and direnv to inject.
  • Plugins like aws-iam-authenticator need to be on $PATH of whatever shell runs kubectl. Watch out for this in CI — kubectl won’t fail until you actually use it.
  • kubectl caches — 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=default

Why this matters:

  • Most production traffic is in a specific namespace (not default). Setting it per-context saves typing and prevents accidents.
  • default is dangerous — many tools deploy there by default, and you don’t want your kubectl delete to 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 *.kubeconfig to .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 proxy on a shared host — it exposes the apiserver on localhost: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 nodes

For 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/config

If 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