
kubectl
How KUBECONFIG merges multiple files (first wins, not last)
Set KUBECONFIG to several files and kubectl merges them by a rule that surprises most people: the first file to define a name wins, and later files are ignored.
You can point KUBECONFIG at several files at once and kubectl will treat them as
one configuration:
export KUBECONFIG=~/.kube/config:~/.kube/work.yaml:~/.kube/prod.yaml
Most people assume this behaves like PATH-style overriding, where the last
definition wins. It is the opposite. The first file to define a given name wins
outright, and every later definition of that name is discarded silently.
That single inversion is behind almost every “why am I connected to the wrong cluster” story.
The rules, precisely
Stated as prose, because it is worth being exact:
- Files are read left to right, separated by
:(;on Windows). - Merging happens per named entry, independently for
clusters,usersandcontexts. The first file to define the nameproductionas a cluster wins; a different file can still win for the user namedproduction. current-contextcomes from the first file that sets a non-empty one. Later files cannot change it.- Empty or missing files in the list are skipped without complaint.
- Relative paths inside a kubeconfig resolve against that file’s own directory,
not your working directory. A
certificate-authority: ca.crtin~/.kube/work/configmeans~/.kube/work/ca.crt, regardless of where you runkubectlfrom.
Rule 5 is the one that breaks when people reorganise their ~/.kube folder, and
the error it produces — a TLS failure — points nowhere near the cause.
The collision that actually bites you
Cluster provisioners are lazy about naming. kind names its cluster entries
kind-<name>, which is fine, but plenty of tools do not bother:
minikubewrites a cluster and user both calledminikube- Many on-prem and kubeadm setups write a cluster called
kubernetesand a user calledkubernetes-admin - Hand-written configs love
default
Put two of those in KUBECONFIG and one of them simply disappears. The context
name from the second file may still exist — contexts and clusters are merged
separately — so you get a context called staging that points at a cluster entry
called kubernetes which was defined by the first file, meaning it points at
production.
Everything looks correct. kubectl config get-contexts shows staging. You are
talking to the wrong cluster.
Check for it directly:
kubectl config view -o jsonpath='{range .contexts[*]}{.name}{"\t→\t"}{.context.cluster}{"\n"}{end}'
Then confirm the server URL behind the cluster your context names:
kubectl config view -o jsonpath='{.clusters[?(@.name=="kubernetes")].cluster.server}'
If the URL is not the one you expect, you have a collision. Renaming is the only
real fix: kubectl config rename-context handles contexts, but cluster and user
names have to be edited in the file.
See what the merge produced
kubectl config view always shows the merged result, never a single file. That
makes it the tool for answering “what does kubectl actually think right now”:
kubectl config view # merged, credentials redacted
kubectl config view --raw # merged, credentials included — careful
kubectl config view --minify # only the current context and what it uses
kubectl config get-contexts # tabular, marks the current one with *
--minify combined with --flatten is the safest way to hand one cluster’s access
to someone without leaking the other nineteen:
kubectl config view --minify --flatten > handover.yaml
What --flatten is for
--flatten inlines every file reference — certificates, keys, token files — as
base64 *-data fields, producing a config that is self-contained and portable.
Without it, you send a file full of paths to certificates that only exist on your
laptop, which is the usual reason a shared kubeconfig fails immediately for the
person you sent it to.
To permanently combine several files into one:
KUBECONFIG=~/.kube/config:~/.kube/work.yaml kubectl config view --flatten > ~/.kube/merged.yaml
Back up the originals first. And remember the merge rule applies here too — if the files collide, the merged output silently contains only the winners.
Where do writes go?
kubectl config set-context, use-context and friends have to pick a file to
modify. The behaviour with multiple files:
- If the entry being modified already exists in one of the files, that file is updated in place.
- If it is new, it is written to the first file in the list.
So kubectl config use-context prod changes current-context in your first file,
which is usually ~/.kube/config. That is also why a use-context sometimes
appears to do nothing: you set it in the first file, but a different first file
was already supplying current-context in the session where you checked.
--kubeconfig turns merging off
The --kubeconfig flag does not add a file to the chain — it replaces the
chain entirely. kubectl --kubeconfig ~/.kube/prod.yaml get pods sees that file
and nothing else, ignoring KUBECONFIG and ~/.kube/config completely.
This makes it the reliable way to run something against exactly one cluster
without wondering what else is in scope. It is a good habit for anything
destructive, and a good habit in scripts, where an inherited KUBECONFIG from the
calling shell is a genuine hazard.
The full precedence chain, highest first:
--kubeconfigflag — used alone, no mergingKUBECONFIGenvironment variable — merged left to right~/.kube/config
A saner setup than one giant file
The pattern that holds up over time is one file per cluster, named after the cluster, never merged into a single blob:
~/.kube/
config # personal / local only
work-eu.yaml
work-us.yaml
prod.yaml
Then select rather than merge:
alias kprod='kubectl --kubeconfig ~/.kube/prod.yaml'
You give up cross-cluster context switching, and you gain the guarantee that a command can only ever reach one cluster. For production access that is a trade worth making — and it eliminates name collisions entirely, because nothing is being merged.
Why we care about this exactly
KubeGlance reads your kubeconfig and nothing else — no agent in the cluster, no
account, no telemetry — so it has to implement these merge semantics identically to
kubectl. If the app resolved a context to a different cluster than kubectl would,
it would be showing you a different reality than your terminal, which is a
worse-than-useless dashboard.
It also picks up the sibling files sitting in ~/.kube/ that are not in your
KUBECONFIG, because in practice that is where most people’s other clusters
live — and offers each one as an importable source rather than merging them behind
your back. Names that would have collided stay distinct, and you keep the per-file
separation described above.
If you want to understand more of what kubectl computes rather than fetches, the STATUS column is the other big one: how kubectl decides what to put in the STATUS column. And when a context is right but the connection still fails, the cause is often credentials rather than config — scoping a read-only kubeconfig covers what a kubeconfig actually grants.
Several clusters, several kubeconfig files? KubeGlance imports each one as a separate source and keeps them distinct — merged into one live view on Mac, iPhone and iPad.
Get KubeGlanceThe Kubernetes dashboard that fits in your pocket
KubeGlance is a native Kubernetes client for iPhone and iPad — the real dashboard, not a companion — with a full Mac app on the same core. Pods, workloads, logs and events, straight from your kubeconfig.
Download KubeGlance

