The problem Kustomize solves¶
Say you have a Kubernetes Deployment manifest for an app. It works fine in dev. Now you need the same app in staging and production — same basic shape, but production needs 5 replicas instead of 1, a different image tag, an extra environment variable, and a different resource limit.
The naive approach is to copy the whole YAML file three times (deployment-dev.yaml, deployment-staging.yaml, deployment-prod.yaml) and hand-edit each. That works right up until you need to change something common to all three — now you're editing three files and hoping you didn't miss one or introduce a typo-driven drift between environments.
Kustomize solves this by letting you define one canonical, environment-agnostic set of manifests (a base), and then layer small, explicit patches on top per environment (overlays) — without ever copying the original YAML or using template placeholders ({{ .Values.replicas }}-style syntax) inside it. You keep plain, valid Kubernetes YAML at every layer; Kustomize's job is purely to combine and transform it.
Kustomize vs. Helm — the actual difference¶
This is the first thing people new to Kustomize want to know, since Helm solves an overlapping problem:
| Helm | Kustomize | |
|---|---|---|
| Approach | Templating (Go templates inject values into YAML) | Patching (plain YAML, overlaid/patched declaratively) |
| Base files | Templates aren't valid YAML until rendered | Every file is always valid, real Kubernetes YAML |
| Packaging | Charts — versioned, shareable, published to repos | No packaging/versioning concept; just directories |
| Logic | Supports conditionals, loops, functions in templates | Deliberately no templating logic at all |
| Built into kubectl | No (separate binary) | Yes — kubectl apply -k (and kubectl kustomize) |
| Best fit | Distributing a reusable, configurable app to others | Managing your own app's per-environment variants |
A common real-world pattern is actually both together: use Helm to install a third-party chart (e.g. ingress-nginx, prometheus), then use Kustomize on top to patch small environment-specific tweaks into the rendered output, or to manage your own in-house app's manifests without needing templating logic.
Core concepts¶
kustomization.yaml¶
Every directory Kustomize operates on needs a kustomization.yaml file — this is what makes it a "kustomization" rather than just a folder of YAML. It lists which resource files to include and what transformations to apply.
# base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml
Bases¶
A base is a directory containing a kustomization.yaml plus the plain Kubernetes manifests it references. It represents the common, environment-agnostic version of your app.
base/
├── kustomization.yaml
├── deployment.yaml
└── service.yaml
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
Overlays¶
An overlay is a directory that references a base and layers changes on top of it for a specific context (an environment, a cluster, a customer). It has its own kustomization.yaml that points back at the base.
overlays/
├── dev/
│ └── kustomization.yaml
├── staging/
│ └── kustomization.yaml
└── production/
├── kustomization.yaml
└── replica-patch.yaml
# overlays/production/kustomization.yaml
resources:
- ../../base
patches:
- path: replica-patch.yaml
# overlays/production/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5
Kustomize merges this patch into the base Deployment, producing a final manifest identical to the base except replicas: 5. The base file itself is never touched or duplicated.
Building and applying¶
kubectl kustomize overlays/production/
This prints the fully merged, final YAML to stdout without applying anything — useful for reviewing exactly what will be sent to the cluster.
kubectl apply -k overlays/production/
The -k flag tells kubectl to run the manifests through Kustomize before applying — kubectl has had Kustomize built in since v1.14, so no separate binary is required for this basic usage (though the standalone kustomize CLI has some newer/extra features ahead of what's vendored into kubectl).
Patch types¶
Strategic merge patch¶
The style shown above — write a partial resource with the same apiVersion/kind/metadata.name, and Kustomize merges it field-by-field into the matching base resource. This is the most common and readable patch style, and it's how most simple overrides (replica count, image tag, an added env var) are done.
JSON 6902 patch¶
For more surgical changes — especially removing a field, or patching something strategic-merge can't cleanly target — a JSON Patch (RFC 6902) gives you explicit add/remove/replace operations against a path in the document:
# overlays/production/kustomization.yaml
patches:
- target:
kind: Deployment
name: my-app
patch: |-
- op: replace
path: /spec/replicas
value: 5
Use this when you need to target something by array index, remove a field entirely, or when a strategic merge patch would ambiguously match more than intended.
Generators¶
Kustomize can generate ConfigMaps and Secrets from files or literals, rather than you hand-writing the YAML (and, importantly, automatically appends a content hash to the generated resource's name):
# base/kustomization.yaml
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
- FEATURE_FLAG_X=enabled
secretGenerator:
- name: app-secret
envs:
- secrets.env
This produces a ConfigMap named something like app-config-8dtcggcgtb — the hash suffix changes whenever the ConfigMap's content changes. Any Deployment referencing app-config by name gets that reference automatically rewritten to match the current hash, which is what forces a pod restart/rollout whenever the config content actually changes (Kubernetes doesn't automatically restart pods just because a referenced ConfigMap's contents changed — the changing name is what triggers it).
Common transformers¶
These apply broadly across every resource in a kustomization, without needing a separate patch file per resource:
# overlays/production/kustomization.yaml
namePrefix: prod-
namespace: production
commonLabels:
environment: production
commonAnnotations:
team: platform
images:
- name: my-app
newTag: v1.4.2
namePrefix/nameSuffix— prepend/append a string to every resource's name (e.g. distinguishingprod-my-appfromdev-my-appif both land in the same namespace, or just for clarity).namespace— override the namespace for every resource, without editing each one.commonLabels/commonAnnotations— apply the same label/annotation set across every resource, and (for labels) also update matchingselectorfields consistently.images— override an image's tag/digest/repository, without needing a strategic merge patch just to change one string.
Layering multiple overlays (components)¶
For cases where a piece of configuration is optional and shared across several overlays (rather than being one-per-environment), Kustomize has a Components feature — a reusable chunk of patches/resources that any overlay can opt into:
# components/debug-logging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- path: debug-log-level-patch.yaml
# overlays/staging/kustomization.yaml
resources:
- ../../base
components:
- ../../components/debug-logging
This avoids duplicating the same patch across multiple overlays that all happen to want the same optional behavior.
Typical directory layout¶
A common, widely-used convention:
app/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
└── overlays/
├── dev/
│ └── kustomization.yaml
├── staging/
│ ├── kustomization.yaml
│ └── replica-patch.yaml
└── production/
├── kustomization.yaml
├── replica-patch.yaml
└── resource-limits-patch.yaml
Each overlay's kustomization.yaml references ../../base and layers on only what's actually different for that environment — dev might need nothing beyond the base at all, in which case its kustomization.yaml can be as simple as:
# overlays/dev/kustomization.yaml
resources:
- ../../base
Debugging and inspection¶
kubectl kustomize <dir>— render the final merged YAML without applying it. Always run this beforeapply -kwhen something looks off, so you're debugging against the actual output rather than guessing.kustomize build <dir>— same as above via the standalone CLI, which sometimes supports newer flags/features than the version vendored intokubectl.- A patch silently not applying is almost always a
kind/name/apiVersionmismatch between the patch and the target resource — Kustomize matches patches to resources by these fields, and a typo there just means the patch matches nothing, with no error.
When Kustomize might not be the right fit¶
- Distributing your app for other people/teams to install with their own configuration values — Helm's chart packaging, versioning, and templating (loops, conditionals) are a better fit for that use case than Kustomize's patch-based model.
- Needing actual logic in your manifests (e.g. "only add this resource if X") — Kustomize deliberately has no templating language, so conditional inclusion has to be modeled as separate overlays/components rather than an
ifstatement.
Quick reference¶
| Term | Meaning |
|---|---|
kustomization.yaml |
The file that makes a directory a kustomization; lists resources and transformations |
| Base | The common, environment-agnostic set of manifests |
| Overlay | A directory that references a base and layers environment-specific changes on top |
| Strategic merge patch | Field-by-field YAML merge patch — the common case |
| JSON 6902 patch | Explicit add/remove/replace operations by path — for surgical edits |
| Generator | Produces a ConfigMap/Secret with a content-hash suffix in its name |
| Transformer | A broad, non-patch-file change applied across all resources (labels, namespace, image tag) |
| Component | A reusable, optional chunk of config any overlay can opt into |
Further reading¶
- https://kustomize.io
- https://kubectl.docs.kubernetes.io/references/kustomize/
- https://github.com/kubernetes-sigs/kustomize/tree/master/examples