31.1 Kustomize Concepts: Base, Overlays, and Patches
Right, let’s get our hands dirty with the core of Kustomize: Bases, Overlays, and Patches. Forget everything you’ve heard about templating engines with their curly braces and logic-less nonsense. Kustomize’s approach is so brilliantly simple it feels like cheating. You’re not generating YAML from templates; you’re customizing existing, valid YAML. It’s the difference between baking a cake from a recipe and expertly decorating one that’s already baked. The latter is faster, less error-prone, and you get to taste-test immediately.
The entire philosophy rests on three concepts: a Base, which is your source of truth, an Overlay, which is your target environment, and Patches, which are the instructions for how to transform the Base into the Overlay.
What in the World is a Base?
Think of a Base as the common, vanilla definition of your application. It’s the complete, working set of Kubernetes manifests that you’d run in a perfect, default world. It’s your deployment.yaml, your service.yaml, your configmap.yaml—all the good stuff. Crucially, a Base is a valid Kustomization itself. It has a kustomization.yaml file that declares its own resources.
Here’s a classic base structure for a simple web app:
~/my-app-base/
├── kustomization.yaml
├── deployment.yaml
├── service.yaml
└── configmap.yaml
The kustomization.yaml is dead simple. It’s just a manifest manifest.
# ~/my-app-base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
And if you run kustomize build ~/my-app-base, you get a perfectly valid YAML stream combining all three files. This is your foundation. It’s boring, it’s reliable, and you never, ever touch it to add environment-specific nonsense like a production database URL. That’s what overlays are for.
Overlays: Where the Magic (and Specifics) Happen
An Overlay is a directory that depends on one or more Bases. It represents a variant of your application—like development, staging, or production. The overlay’s kustomization.yaml file doesn’t list raw resources; it points to the base(s) and then declares the patches it wants to apply.
Your directory structure now looks like this:
~/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── ...
└── overlays/
├── development/
│ ├── kustomization.yaml
│ └── cpu-patch.yaml
└── production/
├── kustomization.yaml
└── replica-patch.yaml
The overlay’s kustomization file is where the real work is declared.
# ~/overlays/development/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# The crucial part: what are we customizing?
bases:
- ../../base
# What changes are we making?
patchesStrategicMerge:
- cpu-patch.yaml
# Maybe we need a completely new resource just for dev?
resources:
- dev-only-ingress.yaml
When you build this overlay (kustomize build ~/overlays/development), Kustomize first loads the entire base, then applies the patches and adds the new resources on top of it. The output is a complete, customized manifest set for your development environment.
The Art of the Patch: Strategic Merge and JSON Patch
This is the engine. Kustomize primarily uses two patch types, and confusing them is a classic rookie mistake.
1. Strategic Merge Patch (SMP): This is the one you’ll use 90% of the time. You write a partial YAML file that looks exactly like the original manifest but only includes the fields you want to change. The “Strategic” part is key: for fields like containers or env, which are lists, it has special smarts to merge based on the name field, not just appending blindly. It’s brilliantly intuitive.
Let’s patch our base deployment to reduce CPU requests for dev:
# ~/overlays/development/cpu-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app # This name MUST match the resource in the base!
spec:
template:
spec:
containers:
- name: my-app-container # Matches the container name to patch
resources:
requests:
cpu: "100m" # This will override the value from the base
2. JSON Patch: Sometimes you need surgical precision that SMP can’t handle, like renaming a key, adding an item to a list in a specific position, or removing a field. JSON Patch is a standard (RFC 6902) that uses a JSON array of operations. It’s more powerful but far less readable. Use it sparingly, when SMP feels like trying to hammer in a screw.
# ~/overlays/production/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5
Wait, that was an SMP example. It’s just that easy. Here’s a JSON Patch equivalent for the same change, which is hilariously overkill and demonstrates why you should usually use SMP:
# patches/json-patch.yaml
- op: replace
path: /spec/replicas
value: 5
(You’d reference this in your kustomization.yaml with the patchesJson6902 field, which requires also specifying the target group/version/kind and name. See? I told you it was more complex).
The Golden Rule: Always prefer Strategic Merge Patch for readability unless you absolutely need the specific operations of JSON Patch. Your future self, trying to debug a deployment at 2 AM, will thank you.
Common Pitfalls and How to Avoid Them
- The Name Mismatch: Your patches must have the same
apiVersion,kind, andmetadata.nameas the resource in the base you’re trying to patch. This is the most common cause of a patch silently doing nothing. Kustomize doesn’t warn you; it just fails to find a match and moves on. - Bases are Relative: The paths in your overlay’s
basesfield and thekustomize buildcommand are relative to the kustomization.yaml file you’re building. Get your../../right. - Don’t Go Patch-Happy: It’s tempting to create a patch for every tiny change. For simple, static value changes like environment variables or replica counts, use
configMapGeneratororreplicasfield in your overlay’s kustomization.yaml instead. It keeps things cleaner. - Order of Operations Matters: Kustomize applies patches in the order they are listed in
patchesStrategicMerge. If two patches change the same field, the last one wins. Structure your patches logically.