Managing Kubernetes Config With Kustomize Overlays
Copy-pasting manifests per environment is how config drift starts. Here's how I structure Kustomize bases and overlays to keep environments honest.
- #kubernetes
- #kustomize
- #gitops
- #configuration
- #overlays
- #yaml
I’ve inherited more than one repo where manifests/dev/, manifests/staging/, and manifests/prod/ were full copies of the same YAML with three values changed in each. It looks fine until someone fixes a bug in prod, forgets staging, and six weeks later a promotion silently reintroduces the bug. The directories drifted because nothing forced them to share a source of truth.
Kustomize exists to kill that pattern. You write the common manifests once as a base, then express each environment as a thin overlay that patches only what differs. No templating language, no {{ }} — just plain YAML layered on plain YAML. Here’s how I structure it in practice.
Base plus overlays
The layout I reach for:
.
├── base
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── service.yaml
└── overlays
├── staging
│ └── kustomization.yaml
└── prod
├── kustomization.yaml
└── replicas-patch.yaml
The base is a complete, deployable set of manifests with sensible defaults. Its kustomization.yaml just lists the resources:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app: payments
An overlay references the base and layers changes on top:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: payments-prod
namePrefix: prod-
replicas:
- name: payments
count: 6
images:
- name: payments
newTag: "1.8.3"
Render it and you see exactly what will apply:
kubectl kustomize overlays/prod
# or apply directly
kubectl apply -k overlays/prod
The replicas, images, and namespace transformers above are built in — you don’t write a patch for common changes like scaling or pinning an image tag. The base stays the single source of truth, and the prod overlay says only “more replicas, newer tag, different namespace.”
Strategic merge vs JSON patches
For changes the built-in transformers don’t cover, you have two patch styles, and knowing when to use each saves a lot of frustration.
Strategic merge patches look like a partial version of the object. They’re readable and good for adding or overriding fields:
# overlays/prod/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments
spec:
template:
spec:
containers:
- name: payments
resources:
requests: { cpu: "500m", memory: "512Mi" }
limits: { memory: "1Gi" }
JSON 6902 patches are surgical and necessary when you need to operate on a specific list element or remove a field:
patches:
- target:
kind: Deployment
name: payments
patch: |-
- op: remove
path: /spec/template/spec/containers/0/livenessProbe
- op: replace
path: /spec/template/spec/containers/0/args/1
value: "--max-conns=200"
The trap with strategic merge and lists: merging by position is fragile. When you’re editing one container in a multi-container pod, or one env var in a long list, a JSON patch with an explicit path is more predictable than hoping the merge keys line up.
Generators for config and secrets
Hardcoding ConfigMaps in the base and overriding them per environment is awkward. Use generators instead, which also give you automatic hash suffixes that trigger rolling updates when config changes:
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
- FEATURE_FLAGS=billing,exports
secretGenerator:
- name: app-secrets
envs:
- secrets.env
The hash suffix is the underrated feature here. Change a value in the ConfigMap, the generated name changes, and the Deployment that references it rolls automatically. No more “I updated the ConfigMap but the pods still have the old value” because nothing restarted.
Components for optional features
When a feature is shared by some environments but not all — say, a sidecar you want in staging and prod but not dev — a Kustomize component keeps it DRY:
# components/metrics/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- path: add-metrics-sidecar.yaml
Overlays opt in by listing it under components:. This avoids the worst Kustomize anti-pattern: a tangle of base-of-bases where you can no longer tell what any given overlay actually produces.
Keep overlays thin and diffable
A few rules that have kept Kustomize repos sane for me:
- The base must be deployable on its own. If the base only works after an overlay patches it, your layering is upside down.
- Overlays should be short. If a prod overlay is longer than the base, the base has environment-specific assumptions baked in that should move up.
- Diff before you apply.
kubectl diff -k overlays/prodshows what will actually change against the live cluster. - Render in CI. Run
kubectl kustomizeon every overlay in your pipeline and validate the output with kubeconform so a broken overlay fails before it merges.
Where AI helps
The most error-prone Kustomize work is writing patches — especially JSON 6902 paths, where an off-by-one in /containers/0/... silently patches the wrong container. I’ll paste the base object and describe the change I want, and let the model produce the patch with the correct path. It’s also handy for the reverse: paste a rendered overlay and ask “what differs from the base and why,” which is a fast way to audit an inherited repo. Running rendered overlays through our AI code review tool catches the subtle ones — a patch that targets a name that no longer exists, or a generator that won’t trigger the rollout you expect.
Kustomize won’t stop you from drifting if you keep copy-pasting, but used properly it removes the reason to copy-paste. One base, thin overlays, and every environment provably built from the same source. For more on structuring Kubernetes config, see our Kubernetes and Helm guides.
AI-generated patches are assistive. Always render and diff overlays against your live cluster before applying.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.