KubeGlanceDownload
How KUBECONFIG merges multiple files (first wins, not last)

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.

· 8 min read

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

Files are read left to right. First definition of a name wins; later ones are discarded without a warning.

Stated as prose, because it is worth being exact:

  1. Files are read left to right, separated by : (; on Windows).
  2. Merging happens per named entry, independently for clusters, users and contexts. The first file to define the name production as a cluster wins; a different file can still win for the user named production.
  3. current-context comes from the first file that sets a non-empty one. Later files cannot change it.
  4. Empty or missing files in the list are skipped without complaint.
  5. Relative paths inside a kubeconfig resolve against that file’s own directory, not your working directory. A certificate-authority: ca.crt in ~/.kube/work/config means ~/.kube/work/ca.crt, regardless of where you run kubectl from.

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:

  • minikube writes a cluster and user both called minikube
  • Many on-prem and kubeadm setups write a cluster called kubernetes and a user called kubernetes-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:

  1. --kubeconfig flag — used alone, no merging
  2. KUBECONFIG environment variable — merged left to right
  3. ~/.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 KubeGlance
#kubeconfig #kubectl #contexts #multi-cluster

The 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