Kustomize: Configuration via Overlays
- Template-Free Customization: Kustomize modifies Kubernetes YAML without introducing any templating syntax. Every file remains valid, plain YAML.
- Base vs. Overlays: You define a "Base" containing common resource definitions and use "Overlays" to apply environment-specific patches (e.g., dev vs. staging vs. prod).
- Native Integration: Kustomize is built directly into
kubectl(kubectl apply -k), making it accessible without installing any additional tools. - Patch Types: Kustomize supports strategic merge patches (partial YAML that merges into the base) and JSON patches (precise operations on specific paths) for maximum flexibility.
- Generators:
configMapGeneratorandsecretGeneratorautomatically create ConfigMaps and Secrets with content-based hash suffixes, enabling safe rolling updates on config changes. - Transformers: Built-in transformers like
namePrefix,nameSuffix,commonLabels, andcommonAnnotationslet you modify all resources in a kustomization at once.
As you move from development to production, your application YAMLs will inevitably change. Replica counts increase, resource limits get tuned, image tags shift from latest to pinned versions, and configuration values differ per environment.
Kustomize provides a structured, template-free way to manage these differences.
1. Bases and Overlays
Visualize how a single Base file can be transformed into multiple environments.
apiVersion: v1 kind: Service metadata: name: my-app spec: type: ClusterIP
2. Why Kustomize?
Unlike Helm, which uses a complex template language, Kustomize is pure YAML.
- No Templates: You don't have to learn a new syntax. Your base manifests are valid Kubernetes YAML that you can
kubectl applydirectly. - Native Support: Run it via
kubectl apply -k .with no additional binary required (Kustomize has been built into kubectl since v1.14). - Readable: Overlays clearly show exactly what is changing compared to the base. A reviewer can look at a patch and immediately understand what differs in production.
- Composable: You can layer overlays on top of other overlays, and reference remote bases from Git repositories.
3. The kustomization.yaml File
Every Kustomize directory must contain a kustomization.yaml file. This file declares which resources to include and what transformations to apply.
Base kustomization.yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
commonLabels:
app.kubernetes.io/name: my-web-app
app.kubernetes.io/managed-by: kustomize
Overlay kustomization.yaml
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
patches:
- path: increase-replicas.yaml
- path: set-resources.yaml
images:
- name: my-web-app
newName: registry.example.com/my-web-app
newTag: "2.1.0"
configMapGenerator:
- name: app-config
behavior: merge
literals:
- LOG_LEVEL=warn
- ENABLE_DEBUG=false
4. Real-World Directory Layout
A typical multi-environment project looks like this:
k8s/
base/
kustomization.yaml
deployment.yaml
service.yaml
configmap.yaml
hpa.yaml
overlays/
dev/
kustomization.yaml
increase-replicas.yaml # patch: replicas: 2
dev-config.yaml # patch: env vars for dev
staging/
kustomization.yaml
increase-replicas.yaml # patch: replicas: 3
staging-ingress.yaml # additional resource
production/
kustomization.yaml
increase-replicas.yaml # patch: replicas: 10
set-resources.yaml # patch: CPU/memory limits
production-ingress.yaml # additional resource
hpa-patch.yaml # patch: HPA min/max
Each overlay's kustomization.yaml references ../../base as its base and adds environment-specific patches or resources.
5. Key Concepts in Depth
Base
The "gold standard" version of your application manifests. The base should contain the most common, environment-agnostic configuration. It should be deployable on its own (e.g., for local development).
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app
spec:
replicas: 1
selector:
matchLabels:
app: my-web-app
template:
metadata:
labels:
app: my-web-app
spec:
containers:
- name: my-web-app
image: my-web-app:latest
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: app-config
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
Overlays
Directories representing environments. Each overlay folder contains a kustomization.yaml that references the base and applies modifications through patches, generators, and transformers.
6. Patch Types
Kustomize supports two types of patches for modifying base resources.
Strategic Merge Patch
A strategic merge patch is a partial YAML document that is merged with the base resource. You only need to specify the fields you want to change. Kubernetes-aware merging handles lists intelligently (e.g., merging containers by name rather than replacing the entire list).
# overlays/production/increase-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app
spec:
replicas: 10
Reference it in the overlay's kustomization.yaml:
patches:
- path: increase-replicas.yaml
You can also write inline patches directly in kustomization.yaml:
patches:
- target:
kind: Deployment
name: my-web-app
patch: |
- op: replace
path: /spec/replicas
value: 10
JSON Patch (RFC 6902)
JSON patches provide precise, surgical operations. They are useful when you need to add, remove, or replace a specific element in an array by index, or when strategic merge does not give you enough control.
# overlays/production/add-sidecar.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app
Referenced with a JSON patch in kustomization.yaml:
patches:
- target:
kind: Deployment
name: my-web-app
patch: |
- op: add
path: /spec/template/spec/containers/-
value:
name: log-shipper
image: fluent/fluent-bit:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- op: add
path: /spec/template/spec/volumes/-
value:
name: shared-logs
emptyDir: {}
JSON patch operations: add, remove, replace, move, copy, test.
7. Generators
Generators create Kubernetes resources (ConfigMaps, Secrets) from files, literals, or environment files. The generated resources automatically get a content-based hash suffix appended to their name (e.g., app-config-8g2h5k). This ensures that when the content changes, Pods referencing the ConfigMap get restarted because the name in the Deployment's envFrom or volumeMounts changes.
configMapGenerator
# kustomization.yaml
configMapGenerator:
- name: app-config
literals:
- DATABASE_HOST=postgres.databases.svc
- LOG_LEVEL=info
- MAX_CONNECTIONS=100
- name: nginx-config
files:
- configs/nginx.conf
- name: app-env
envs:
- configs/app.env # KEY=VALUE format, one per line
secretGenerator
# kustomization.yaml
secretGenerator:
- name: db-credentials
literals:
- username=admin
- password=supersecret
type: Opaque
- name: tls-cert
files:
- tls.crt=certs/server.crt
- tls.key=certs/server.key
type: kubernetes.io/tls
To disable the hash suffix (not recommended, but sometimes necessary):
generatorOptions:
disableNameSuffixHash: true
8. Transformers
Transformers modify all resources in a kustomization uniformly.
namePrefix and nameSuffix
Add a prefix or suffix to every resource name:
# overlays/production/kustomization.yaml
namePrefix: prod-
nameSuffix: -v2
A Deployment named my-web-app becomes prod-my-web-app-v2. Kustomize automatically updates all cross-references (e.g., Service selectors, ConfigMap references).
commonLabels
Add labels to every resource and update all selectors:
commonLabels:
environment: production
team: platform
Caution: commonLabels also modifies spec.selector.matchLabels on Deployments and Services. Since matchLabels is immutable on existing Deployments, adding commonLabels after the initial deployment can break updates. Use labels (without selector modification) if you only want metadata labels:
labels:
- pairs:
environment: production
team: platform
includeSelectors: false
commonAnnotations
Add annotations to all resource metadata:
commonAnnotations:
config.kubernetes.io/origin: "kustomize overlay production"
owner: platform-team@example.com
9. Image Overrides
The images transformer lets you change container image names, tags, or digests without writing a patch:
# overlays/production/kustomization.yaml
images:
- name: my-web-app # original image name
newName: registry.example.com/my-web-app # new registry/name
newTag: "2.1.0" # new tag
- name: nginx
newTag: "1.25-alpine"
- name: redis
newName: my-registry.example.com/redis
digest: sha256:abc123def456... # pin by digest
Kustomize scans all container specs across all resources and updates any matching image references. This is particularly useful in CI/CD pipelines where you dynamically set the image tag:
cd overlays/production
kustomize edit set image my-web-app=registry.example.com/my-web-app:${GIT_SHA}
kubectl apply -k .
10. Using with kubectl
Kustomize is built into kubectl. You do not need to install the standalone kustomize binary for basic usage.
# Preview the rendered output (dry run)
kubectl kustomize overlays/production
# Apply the rendered output to the cluster
kubectl apply -k overlays/production
# Diff against the current cluster state
kubectl diff -k overlays/production
# Delete all resources defined in the kustomization
kubectl delete -k overlays/production
However, the standalone kustomize binary is typically more up-to-date than the version bundled in kubectl. If you need the latest features:
# Install standalone kustomize
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
# Use it to build and pipe to kubectl
kustomize build overlays/production | kubectl apply -f -
11. Multi-Environment Workflow Example
Here is a complete workflow demonstrating how a team manages dev, staging, and production with Kustomize:
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
- ENABLE_DEBUG=true
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: dev-
images:
- name: my-web-app
newTag: latest
# overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: staging-
patches:
- path: replicas-patch.yaml
images:
- name: my-web-app
newTag: "2.1.0-rc1"
configMapGenerator:
- name: app-config
behavior: merge
literals:
- ENABLE_DEBUG=false
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- hpa.yaml # Production-only HPA
namePrefix: prod-
patches:
- path: replicas-patch.yaml
- path: resources-patch.yaml
images:
- name: my-web-app
newName: registry.example.com/my-web-app
newTag: "2.1.0"
configMapGenerator:
- name: app-config
behavior: merge
literals:
- LOG_LEVEL=warn
- ENABLE_DEBUG=false
Deploy to each environment:
kubectl apply -k overlays/dev
kubectl apply -k overlays/staging
kubectl apply -k overlays/production
12. Kustomize vs Helm
| Aspect | Kustomize | Helm |
|---|---|---|
| Philosophy | Patch plain YAML -- no abstraction layer | Template engine -- generate YAML from logic + values |
| Syntax | Pure YAML | Go template language |
| Learning Curve | Low -- if you know YAML, you know Kustomize | Higher -- requires learning Go templates and chart conventions |
| Package Distribution | No built-in package concept | Full package manager with repos, versioning, OCI support |
| Lifecycle Management | None -- relies on kubectl apply | install, upgrade, rollback, uninstall with revision history |
| Secret Management | secretGenerator (plain text in repo, or use SOPS) | helm-secrets plugin, or pass via --set |
| Tooling Required | Built into kubectl | Requires helm binary |
| Best For | Your own app manifests, per-environment config | Third-party apps, complex parameterized packages |
When to Choose Kustomize
- You are managing your own application's manifests and want minimal abstraction.
- Your team is comfortable with YAML and does not want to learn Go templates.
- You need simple per-environment changes (replica counts, image tags, resource limits).
- You want to use
kubectlwithout additional tooling.
When to Choose Helm
- You need to distribute a reusable package that others will install with different configurations.
- You need lifecycle management (rollback, revision history).
- You are installing complex third-party applications (databases, monitoring stacks).
- You need conditional logic (include/exclude entire resources based on flags).
Many teams use both: Helm for third-party dependencies and Kustomize for their own application manifests.
Common Pitfalls
-
commonLabelsbreaking Deployment updates: AddingcommonLabelsmodifiesspec.selector.matchLabels, which is immutable on existing Deployments. If you addcommonLabelsafter the initial deploy,kubectl applywill fail. Use thelabelstransformer withincludeSelectors: falseto add labels without modifying selectors. -
Forgetting
behavior: mergein generators: By default, aconfigMapGeneratorin an overlay replaces the base's ConfigMap entirely. To add or override specific keys while keeping the base keys, setbehavior: merge. -
Hash suffix surprises: Generated ConfigMaps and Secrets get a hash suffix. If you reference them by hardcoded name in resources that Kustomize does not manage (e.g., a CronJob defined outside the kustomization), the reference will be broken. Either include the referencing resource in the kustomization or disable hash suffixes.
-
Relative path errors: Resources and patches use paths relative to the
kustomization.yamlfile. A common mistake is using paths relative to the project root. If your overlay is atoverlays/prod/kustomization.yamland your base is atbase/, the reference should be../../base, notbase/. -
Not previewing before applying: Always run
kubectl kustomize overlays/productionorkubectl diff -k overlays/productionbefore applying to verify the rendered output matches your expectations. -
Overusing patches for simple changes: If you only need to change an image tag, use the
imagestransformer instead of writing a patch file. It is simpler and less error-prone.
Best Practices
-
Keep bases deployable. The base should work standalone for local development with
kubectl apply -k base/. This makes it easy for developers to test without overlay complexity. -
One overlay per environment. Maintain a clear
overlays/dev,overlays/staging,overlays/productionstructure. Avoid deeply nested overlay chains that are hard to reason about. -
Use
imagesfor tag changes instead of patches. Theimagestransformer is purpose-built for this and keeps your kustomization clean. -
Use
configMapGeneratorandsecretGeneratorinstead of manually managed ConfigMap YAML files. The hash suffix mechanism ensures Pods are restarted when configuration changes. -
Commit rendered output in CI for auditing. Run
kubectl kustomizein your CI pipeline and store the rendered output as a build artifact. This provides an audit trail of exactly what was deployed. -
Use
kubectl diff -kbefore applying in production. This shows the exact changes that will be made to the cluster. -
Avoid
commonLabelson existing workloads. Use thelabelstransformer withincludeSelectors: falseto safely add labels without modifying immutable selectors. -
Pin remote bases to a Git ref. If referencing a remote base (e.g., from a Git URL), always pin to a commit SHA or tag, never
mainormaster.
What's Next?
- Helm -- Learn the template-based alternative for package management and compare approaches for your use case.
- ConfigMaps & Secrets -- Understand the underlying ConfigMap and Secret resources that Kustomize generators create.
- Deployments -- Deep dive into the Deployment resource that Kustomize patches typically modify.
- Storage (PV & PVC) -- Use Kustomize overlays to vary storage sizes and StorageClasses across environments.