
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.
How kubectl loads your config
Before the merge rules make sense, it helps to see what the client actually does. There are two stages people collapse into one: choosing which files to read, then merging them.
The merge is not a deep merge and it is not last-write-wins. Each named entry is claimed once, and the claim is permanent for that invocation.
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

