Use it without installing anything
Kustomize is built into kubectl since 1.14:
kubectl version --client
# v1.32.x — comes with kustomize
# Apply a kustomization
kubectl apply -k ./overlays/prod/
# Just see the rendered YAML without applying
kubectl kustomize ./overlays/prod/
The standalone kustomize binary tracks newer features faster than the kubectl-embedded version. Install if you need the latest:
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
kustomize version
The base directory
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
# Optional: common labels applied to every resource
commonLabels:
app.kubernetes.io/name: myapp
app.kubernetes.io/managed-by: kustomize
resources: lists the raw YAML files. kubectl kustomize ./base outputs all of them concatenated, with the common labels applied to each.
An overlay
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: prod
resources:
- ../../base
namePrefix: prod-
commonAnnotations:
environment: production
team: platform
# Inline patches (strategic merge)
patches:
- target:
kind: Deployment
name: myapp
patch: |-
- op: replace
path: /spec/replicas
value: 5
- op: add
path: /spec/template/spec/containers/0/resources
value:
limits: { memory: 1Gi, cpu: 500m }
requests: { memory: 512Mi, cpu: 250m }
# Or file-based patches
# patches:
# - path: replicas-patch.yaml
# target: { kind: Deployment, name: myapp }
# Image overrides
images:
- name: myorg/myapp
newTag: v1.2.3
# Or newName: ghcr.io/myorg/myapp-prod
# Generate a ConfigMap from a literal value or file
configMapGenerator:
- name: app-config
behavior: merge # merge with the one from base; or "create" or "replace"
literals:
- LOG_LEVEL=info
- SENTRY_ENV=prod
files:
- config/feature-flags.json
secretGenerator:
- name: api-keys
files:
- secrets/stripe.txt
kubectl apply -k ./overlays/prod/ renders + applies.
The directory layout that works
k8s/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
└── overlays/
├── dev/
│ ├── kustomization.yaml
│ └── replicas-1.yaml
├── staging/
│ ├── kustomization.yaml
│ └── replicas-2.yaml
└── prod/
├── kustomization.yaml
├── replicas-5.yaml
└── resources-limits.yaml
Common diffs across overlays usually boil down to: namespace, replicas, image tag, resource limits, env var values, ingress hostname. All cleanly express-able as patches without the base seeing anything environment-specific.
ConfigMap / Secret generators
Kustomize's hashed ConfigMap / Secret generation is the standout feature: when you change a value, the generated name gets a new content-hash suffix. Deployments referencing the ConfigMap by name see a "new" ConfigMap on apply, triggering a rolling update. No manual restart needed when configuration changes.
# In a Deployment that uses the configmap
spec:
template:
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config # kustomize rewrites this to app-config-abc1234
# Disable hashing (if you want a stable name; loses the auto-rollout benefit)
configMapGenerator:
- name: app-config
literals: ["LOG_LEVEL=info"]
options:
disableNameSuffixHash: true
Strategic-merge vs JSON-6902 patches
- Strategic merge (default for file-based patches) — structural patches; "merge this fragment into the matching resource." Knows about Kubernetes' merge semantics (a list of containers merges by name).
- JSON patch (RFC 6902, the
op: replacestyle above) — explicit path operations. More precise; required when you need to replace an entire array or modify by index.
Cross-cutting transformations
# In kustomization.yaml
# Add a label to every resource
labels:
- includeSelectors: true
pairs:
tier: backend
# Add annotations
commonAnnotations:
deployment.kubernetes.io/revision-history: "5"
# Set a namespace for every resource
namespace: my-namespace
# Add a name prefix / suffix
namePrefix: dev-
nameSuffix: -v2
# Replace all references to one image with another (across containers, init-containers, etc.)
images:
- name: nginx
newName: ghcr.io/myorg/nginx
newTag: alpine
Components: reusable cross-cutting features
Beyond base + overlay, Kustomize components are reusable bundles of patches that can be mixed in. Example: a "with-redis-sidecar" component that any overlay can opt-in to:
# overlays/prod/kustomization.yaml
resources:
- ../../base
components:
- ../../components/with-redis-sidecar
- ../../components/with-prometheus-monitor
Components are great for orthogonal features that aren't environment-specific (debug instrumentation, sidecars, monitor resources).
Remote bases
resources:
- github.com/myorg/k8s-charts/base?ref=v1.2.3
Cache + version-pinned reference to a remote base. Useful for shared chart-of-charts patterns across teams.
Validate before applying
# Preview the rendered YAML
kubectl kustomize ./overlays/prod/
# Dry-run + diff against the live cluster
kubectl apply -k ./overlays/prod/ --dry-run=server -o yaml
# Diff against current
kubectl diff -k ./overlays/prod/
Kustomize vs Helm
- Helm — templates with
{{ }}; chart-shaped distribution; broad community-chart ecosystem. The right answer when you're consuming third-party software (cert-manager, ingress-nginx, prometheus-stack) or distributing your app to others. - Kustomize — pure YAML + patches; no templating; built into kubectl. The right answer for your own apps with a small number of environments to manage.
The most common production pattern: Helm for installing third-party software, Kustomize for managing your own apps' per-env diffs. Argo CD (see that tutorial) handles both formats natively from the same Git repo.
Kustomize + Helm together
Kustomize can render Helm charts and then patch the result:
# kustomization.yaml
helmCharts:
- name: redis
repo: oci://registry-1.docker.io/bitnamicharts
version: 21.0.0
releaseName: cache
namespace: myapp
valuesInline:
auth:
password: changeme
patches:
- target:
kind: StatefulSet
name: cache-redis-master
patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources/limits/memory
value: 2Gi
Render the chart, apply patches on top, output as a single stream. Get Helm's chart ecosystem + Kustomize's overlay flexibility.
Worth knowing
- The
commonLabelsfield is now deprecated in favor oflabels:withincludeSelectors.commonLabelsrewrote selector labels too (changing them broke immutable selectors); the newlabels:field is safer. - The order of
resources:matters for ordering of generated output but not for cluster apply (kubectl resolves dependencies). - For very-large kustomizations, the standalone
kustomizebinary is faster than the kubectl-embedded one.