Skip to main content

Kustomize: Configuration via Overlays

Key Takeaways for AI & Readers
  • 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: configMapGenerator and secretGenerator automatically create ConfigMaps and Secrets with content-based hash suffixes, enabling safe rolling updates on config changes.
  • Transformers: Built-in transformers like namePrefix, nameSuffix, commonLabels, and commonAnnotations let 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.

Project Structure
📁 base/
📄 deployment.yaml
📄 kustomization.yaml
📁 overlays/
📁 dev/
📄 patches.yaml
📁 prod/
📄 patches.yaml
Final Manifest
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: ClusterIP
Generated via: kustomize build overlays/base
Kustomize uses a "Patch" strategy. It doesn't use templates like Helm; instead, it overlays specific changes onto a base YAML file.

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 apply directly.
  • 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

AspectKustomizeHelm
PhilosophyPatch plain YAML -- no abstraction layerTemplate engine -- generate YAML from logic + values
SyntaxPure YAMLGo template language
Learning CurveLow -- if you know YAML, you know KustomizeHigher -- requires learning Go templates and chart conventions
Package DistributionNo built-in package conceptFull package manager with repos, versioning, OCI support
Lifecycle ManagementNone -- relies on kubectl applyinstall, upgrade, rollback, uninstall with revision history
Secret ManagementsecretGenerator (plain text in repo, or use SOPS)helm-secrets plugin, or pass via --set
Tooling RequiredBuilt into kubectlRequires helm binary
Best ForYour own app manifests, per-environment configThird-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 kubectl without 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

  1. commonLabels breaking Deployment updates: Adding commonLabels modifies spec.selector.matchLabels, which is immutable on existing Deployments. If you add commonLabels after the initial deploy, kubectl apply will fail. Use the labels transformer with includeSelectors: false to add labels without modifying selectors.

  2. Forgetting behavior: merge in generators: By default, a configMapGenerator in an overlay replaces the base's ConfigMap entirely. To add or override specific keys while keeping the base keys, set behavior: merge.

  3. 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.

  4. Relative path errors: Resources and patches use paths relative to the kustomization.yaml file. A common mistake is using paths relative to the project root. If your overlay is at overlays/prod/kustomization.yaml and your base is at base/, the reference should be ../../base, not base/.

  5. Not previewing before applying: Always run kubectl kustomize overlays/production or kubectl diff -k overlays/production before applying to verify the rendered output matches your expectations.

  6. Overusing patches for simple changes: If you only need to change an image tag, use the images transformer instead of writing a patch file. It is simpler and less error-prone.


Best Practices

  1. 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.

  2. One overlay per environment. Maintain a clear overlays/dev, overlays/staging, overlays/production structure. Avoid deeply nested overlay chains that are hard to reason about.

  3. Use images for tag changes instead of patches. The images transformer is purpose-built for this and keeps your kustomization clean.

  4. Use configMapGenerator and secretGenerator instead of manually managed ConfigMap YAML files. The hash suffix mechanism ensures Pods are restarted when configuration changes.

  5. Commit rendered output in CI for auditing. Run kubectl kustomize in your CI pipeline and store the rendered output as a build artifact. This provides an audit trail of exactly what was deployed.

  6. Use kubectl diff -k before applying in production. This shows the exact changes that will be made to the cluster.

  7. Avoid commonLabels on existing workloads. Use the labels transformer with includeSelectors: false to safely add labels without modifying immutable selectors.

  8. 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 main or master.


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.