▶Prefer to watch? This short video runs this page's main commands in a real lab, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
kustomize build <directory>
kubectl apply -k <directory>
Kustomize customizes plain Kubernetes YAML without templates. You keep a clean "base" set of
manifests, then layer "overlays" on top (dev, staging, prod) that patch, rename, relabel or
re-image things. Everything is ordinary YAML, driven by a file called kustomization.yaml.
It is built into kubectl (kubectl apply -k), and also available as a standalone kustomize
command that is often a newer version.
Install
kubectl version --client # kustomize is built in (kubectl apply -k, kubectl kustomize)
brew install kustomize # macOS and Linux (Homebrew)
sudo snap install kustomize # Linux (snap)
go install sigs.k8s.io/kustomize/kustomize/v5@latest # from source
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash # official script (read it first)
docker run --rm -v "$PWD":/work -w /work registry.k8s.io/kustomize/kustomize:v5.4.3 build . # no install (check the tag)
kustomize version
The version built into kubectl can lag behind the standalone one. Run both --version commands if a feature is missing.
The main commands
kustomize build ./overlays/prod # render the final YAML to the screen
kustomize build ./overlays/prod | kubectl apply -f - # render and apply
kubectl kustomize ./overlays/prod # same render, using kubectl's built-in copy
kubectl apply -k ./overlays/prod # build and apply in one step
kubectl diff -k ./overlays/prod # preview changes against the live cluster
kubectl delete -k ./overlays/prod # delete everything the kustomization creates
kubectl get -k ./overlays/prod # list those resources from the cluster
kustomize build ./overlays/prod > rendered.yaml # save the output (for review or GitOps)
kustomize create --autodetect # generate a kustomization.yaml from the YAML files in a folder
kustomize edit ... # change kustomization.yaml from the command line (see below)
kustomize cfg ... # inspect and manipulate YAML (advanced)
Typical layout
myapp/
base/
kustomization.yaml
deployment.yaml
service.yaml
overlays/
dev/
kustomization.yaml
replica-patch.yaml
prod/
kustomization.yaml
resources-patch.yaml
ingress.yaml
The base is complete and works on its own. Overlays only describe what is different.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production # put everything in this namespace
namePrefix: prod- # rename: prod-web, prod-db ...
commonLabels:
env: production # add labels (and selectors) to everything
commonAnnotations:
owner: platform-team
resources:
- ../../base # pull in the base
- ingress.yaml # extra resources only prod needs
images:
- name: nginx # change an image without editing the YAML
newName: registry.example.com/nginx
newTag: "1.27.2"
replicas:
- name: web
count: 5
patches:
- path: resources-patch.yaml # a patch file (see below)
Fields in kustomization.yaml
resources Files, folders or URLs to include (bases go here too)
namespace Set the namespace on every resource
namePrefix / nameSuffix Add text to every resource name
labels Add labels: - pairs: {env: prod} includeSelectors: true
commonLabels Add labels everywhere (also touches selectors: risky on existing apps)
commonAnnotations Add annotations to everything
images Rename or retag images
replicas Set replica counts by workload name
patches Change parts of resources (strategic merge or JSON patch)
configMapGenerator Create ConfigMaps from files or literals
secretGenerator Create Secrets from files or literals
components Reusable optional pieces you can add to an overlay
replacements Copy a value from one field to another
helmCharts Inflate a Helm chart as part of the build (needs --enable-helm)
generatorOptions Options for generators (disableNameSuffixHash, labels ...)
sortOptions Control the order of the output
transformers / generators / validators Custom plugins (advanced)
Patches
Strategic merge patch (a partial YAML file; matched by kind and name):
# resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
template:
spec:
containers:
- name: web # containers are matched by name
resources:
limits:
cpu: 500m
memory: 512Mi
patches:
- path: resources-patch.yaml
Inline patch with a target (JSON 6902 style):
patches:
- target:
kind: Deployment
name: web
patch: |-
- op: replace
path: /spec/replicas
value: 3
- op: add
path: /spec/template/spec/containers/0/env/-
value: { name: LOG_LEVEL, value: debug }
Operations: add, remove, replace, move, copy, test. Paths use / and array positions (0, -).
Target selectors
target:
kind: Deployment # a resource type
name: web # by name (regular expressions allowed)
namespace: default
labelSelector: "app=web"
annotationSelector: "team=blue"
group: apps
version: v1
Generators (ConfigMaps and Secrets)
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
- MODE=prod
files:
- app.properties
envs:
- app.env
secretGenerator:
- name: db-creds
literals:
- password=change-me
type: Opaque
Generated names get a hash suffix (app-config-7h2k9f). Kustomize rewrites references to them,
so changing the contents changes the name and Pods roll automatically. To turn that off:
generatorOptions:
disableNameSuffixHash: true
Do not commit real secrets. Use sealed secrets, SOPS or an external secrets operator instead.
Images, replicas and labels quickly
images:
- name: myapp
newTag: v2.1.0 # only change the tag
- name: myapp
newName: registry.example.com/team/myapp
digest: sha256:abc123... # pin to a digest
replicas:
- name: web
count: 3
labels:
- pairs:
app.kubernetes.io/part-of: shop
includeSelectors: false # safer: does not touch selectors
includeTemplates: true
Components (reusable add-ons)
# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- path: add-metrics-annotations.yaml
# in an overlay:
components:
- ../../components/monitoring
Use components for optional features you want in some overlays but not others.
Replacements (copy a value from one place to another)
replacements:
- source:
kind: ConfigMap
name: app-config
fieldPath: data.HOSTNAME
targets:
- select:
kind: Ingress
fieldPaths:
- spec.rules.0.host
This replaces the old vars feature. Use it sparingly: too many replacements make overlays hard to follow.
Edit kustomization.yaml from the command line
kustomize edit add resource deployment.yaml
kustomize edit add resource ../../base
kustomize edit set image nginx=nginx:1.27.2
kustomize edit set namespace production
kustomize edit set nameprefix prod-
kustomize edit set replicas web=5
kustomize edit add label env:prod
kustomize edit add patch --path patch.yaml
kustomize edit add configmap app-config --from-literal=KEY=value
kustomize edit remove resource old.yaml
Very handy in CI to set the image tag: (cd overlays/prod && kustomize edit set image myapp=myapp:$GIT_SHA)
Remote bases and Helm
resources:
- github.com/example/repo//deploy/base?ref=v1.2.3 # a folder in a git repo, pinned to a tag
- https://example.com/manifests/app.yaml
Pin remote bases with ?ref=tag or a commit hash. Never track a moving branch in production.
helmCharts:
- name: nginx
repo: https://charts.bitnami.com/bitnami
version: 15.4.0
releaseName: web
namespace: web
valuesInline:
replicaCount: 2
kustomize build --enable-helm ./overlays/prod
Helm inflation needs the helm binary installed and the flag --enable-helm (kubectl kustomize needs it too).
Build options
kustomize build ./overlays/prod --enable-helm # allow helmCharts
kustomize build ./overlays/prod --load-restrictor LoadRestrictionsNone # allow files outside the folder (use with care)
kustomize build ./overlays/prod -o out/ # write each resource to its own file
kustomize build ./overlays/prod --reorder legacy # older ordering behavior
kustomize build ./overlays/prod --enable-alpha-plugins # allow exec/KRM function plugins (trust only known ones)
By default, a kustomization may only reference files inside its own directory (or bases it lists).
Practical one-liners
# See what will be deployed, without touching the cluster
kustomize build overlays/prod
# Deploy an overlay
kubectl apply -k overlays/prod
# Preview the difference against the cluster first
kubectl diff -k overlays/prod
# Compare two environments
diff <(kustomize build overlays/dev) <(kustomize build overlays/prod)
# Set the image tag in CI, then deploy
cd overlays/prod && kustomize edit set image myapp=registry.example.com/myapp:$GIT_SHA && cd - && kubectl apply -k overlays/prod
# Check the output is valid before applying
kustomize build overlays/prod | kubectl apply --dry-run=client -f -
kustomize build overlays/prod | kubectl apply --dry-run=server -f -
# List the kinds and names that will be created
kustomize build overlays/prod | grep -E '^(kind| name):'
# Delete everything an overlay created
kubectl delete -k overlays/prod
# Start a kustomization from existing YAML files
cd myapp/base && kustomize create --autodetect
# Validate the output with a schema tool
kustomize build overlays/prod | kubeconform -strict -summary
# Generate a starter for a new overlay
mkdir -p overlays/staging && printf 'resources:\n - ../../base\n' > overlays/staging/kustomization.yaml
Kustomize vs Helm
Kustomize No templates: patch real YAML. Simple, transparent, great for your own apps and per-environment changes
Helm Templates and packages: great for third-party software and apps with lots of settings
Together Render a chart with helm template (or helmCharts), then patch the result with Kustomize
Argo CD / Flux GitOps controllers: both can deploy Kustomize overlays straight from git
kubectl apply -f Plain files with no layering
Rule of thumb: Kustomize for "my app in three environments", Helm for "install this off-the-shelf app".
Common problems
"no such file or directory" on resources Path is wrong. Paths in kustomization.yaml are relative to that file
"security; file is not in or below" Referencing outside the folder: restructure, or use --load-restrictor (carefully)
"kustomization.yaml" not found The folder you pointed at has no kustomization file
"no matches for OriginalId ..." A patch target does not match by kind, name or namespace (prefixes apply after patching)
Patch has no effect Wrong name, wrong kind or wrong apiVersion in the patch, or a typo in the path
"may not add resource with an already existing id" The same resource is included twice (a base pulled in two ways)
Selector or label changes break a Deployment commonLabels rewrote selectors, which are immutable. Use labels with includeSelectors: false
Pods do not restart when a ConfigMap changes Static ConfigMap: use configMapGenerator so the name hash changes
kubectl apply -k uses old behavior kubectl's built-in kustomize is older. Use the standalone kustomize build | kubectl apply -f -
"helm: executable file not found" Install helm and add --enable-helm
YAML errors Use spaces, not tabs, and check indentation. Try kustomize build to see the exact error
Tips
Always run kustomize build first and read the output. It is just YAML, so you can review exactly what will be applied.
Keep the base valid and deployable on its own. Overlays should only hold differences.
Use kubectl diff -k before kubectl apply -k, especially on production.
Prefer patches, images and replicas over copying whole files into overlays.
Use labels with includeSelectors: false (not commonLabels) on apps that already exist, so selectors do not change.
Use configMapGenerator so a config change automatically rolls the Pods that use it.
Pin remote bases and Helm chart versions (?ref=v1.2.3). Do not point production at a moving branch.
Keep secrets out of git. Use SOPS, sealed secrets or an external secrets manager.
Set image tags from CI with kustomize edit set image, so deploys are traceable to a commit.
Do not let overlays grow tangled. If you need many conditionals and loops, that is a sign Helm might fit better.