Kustomize
Overview
Kustomize is a Kubernetes-native configuration management tool built into kubectl. It works by layering patches and overlays on top of a base of plain Kubernetes YAML — no templating language, no value files, just plain manifests and transformations.
base/ ← plain Kubernetes manifests, no env-specific values
kustomization.yaml
deployment.yaml
service.yaml
configmap.yaml
overlays/
staging/
kustomization.yaml ← patches for staging
replica-patch.yaml
production/
kustomization.yaml ← patches for production
replica-patch.yaml
hpa-patch.yaml
ingress.yaml ← production-only resource
Kustomize transforms are deterministic and diffable — git diff always shows exactly what changed, unlike Helm's Go template rendering.
Base Structure
base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- serviceaccount.yaml
- networkpolicy.yaml
# Common labels added to ALL resources
commonLabels:
app.kubernetes.io/name: payments-api
app.kubernetes.io/managed-by: kustomize
# Common annotations added to ALL resources
commonAnnotations:
team: platform-payments
# Image transformer — update image tags without editing deployment.yaml
images:
- name: payments-api
newName: ghcr.io/acme/payments-api
newTag: latest # overridden per overlay
base/deployment.yaml — Plain YAML, No Templating
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api # no {{ }} — just plain YAML
spec:
replicas: 1 # base default, overridden by overlay
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-api
spec:
containers:
- name: payments-api
image: payments-api:latest # replaced by images transformer
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /readyz
port: 8080
livenessProbe:
httpGet:
path: /healthz
port: 8080
Overlay Structure
overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base # inherit everything from base
namePrefix: staging- # prepend "staging-" to all resource names (optional)
namespace: staging # set namespace on all resources
# Override image tag for staging
images:
- name: payments-api
newName: ghcr.io/acme/payments-api
newTag: sha-abc123 # set by CI after build
# Patches
patches:
- path: replica-patch.yaml
- path: configmap-patch.yaml
# ConfigMap generated from literal values
configMapGenerator:
- name: payments-config
literals:
- LOG_LEVEL=debug
- DATABASE_HOST=postgres-staging.internal
- ENVIRONMENT=staging
options:
disableNameSuffixHash: true
overlays/staging/replica-patch.yaml
# Strategic merge patch — only the fields listed are changed
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
replicas: 1 # staging: 1 replica
overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- ingress.yaml # production-only resource
- hpa.yaml
- pdb.yaml
namespace: production
images:
- name: payments-api
newName: ghcr.io/acme/payments-api
newTag: sha-def456
patches:
- path: replica-patch.yaml
- path: resources-patch.yaml
- path: affinity-patch.yaml
configMapGenerator:
- name: payments-config
literals:
- LOG_LEVEL=info
- DATABASE_HOST=postgres-prod.internal
- ENVIRONMENT=production
options:
disableNameSuffixHash: true
overlays/production/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
replicas: 3 # production: 3 replicas
Patch Types
Strategic Merge Patch (most common)
Merges at the Kubernetes API level — lists are merged by key, not replaced. Good for most cases.
# patches/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
template:
spec:
containers:
- name: payments-api # must match container name to merge
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 1Gi
JSON 6902 Patch (surgical precision)
Operates on specific JSON path. Required when you need to add to a list, remove a key, or do operations not supported by strategic merge.
# kustomization.yaml
patches:
- target:
kind: Deployment
name: payments-api
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: ghcr.io/acme/payments-api:sha-newsha
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: NEW_ENV_VAR
value: "new-value"
- op: remove
path: /spec/template/spec/containers/0/env/2
Patch from File with Target Selector
# kustomization.yaml — apply one patch to multiple resources
patches:
- path: add-prometheus-annotations.yaml
target:
kind: Deployment # applies to ALL Deployments in this overlay
# add-prometheus-annotations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: irrelevant # ignored when target selector is used
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
Generators
ConfigMap Generator
# kustomization.yaml
configMapGenerator:
- name: app-config
files:
- config/app.properties # file content becomes a key
- nginx.conf=config/nginx.conf # custom key name
literals:
- LOG_LEVEL=info
envs:
- .env.staging # key=value file
options:
disableNameSuffixHash: true # prevent Kustomize from appending hash suffix
Secret Generator
# kustomization.yaml
secretGenerator:
- name: db-credentials
literals:
- username=payments-user
- password=changeme # use ESO in production — this is for local dev only
type: Opaque
options:
disableNameSuffixHash: true
Components — Reusable Kustomize Modules
Components are reusable Kustomize configurations that can be included in multiple overlays. Useful for cross-cutting concerns like enabling Istio sidecar injection, adding monitoring annotations, or enabling debug mode.
# components/istio-injection/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- patch: |-
- op: add
path: /spec/template/metadata/labels/sidecar.istio.io~1inject
value: "true"
target:
kind: Deployment
# components/prometheus-scrape/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- patch: |-
- op: add
path: /metadata/annotations/prometheus.io~1scrape
value: "true"
- op: add
path: /metadata/annotations/prometheus.io~1port
value: "8080"
target:
kind: Service
# overlays/production/kustomization.yaml — include components
components:
- ../../components/istio-injection
- ../../components/prometheus-scrape
Image Tag Updates in CI
# Update image tag in a specific overlay using kustomize edit
cd overlays/staging
kustomize edit set image payments-api=ghcr.io/acme/payments-api:sha-${GIT_SHA}
# Verify the change
kustomize build overlays/staging | grep "image:"
# Commit and push (triggers Argo CD sync)
git add kustomization.yaml
git commit -m "chore(payments-api): bump staging to sha-${GIT_SHA}"
git push
GitHub Actions: Update and PR
- name: Update kustomize image tag
run: |
cd k8s-config/services/payments-api/staging
kustomize edit set image \
payments-api=ghcr.io/acme/payments-api:sha-${{ github.sha }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore(staging): bump payments-api to sha-${{ github.sha }}"
branch: bump/payments-api-staging-${{ github.sha }}
Kustomize Build and Diff
# Render manifests for an overlay (dry-run, no apply)
kustomize build overlays/production
# Apply directly
kustomize build overlays/production | kubectl apply -f -
# kubectl kustomize (built-in, same as kustomize build)
kubectl kustomize overlays/production
# Apply via kubectl
kubectl apply -k overlays/production
# Diff against live cluster
kustomize build overlays/production | kubectl diff -f -
Kustomize vs Helm — When to Use Each
| Criterion | Kustomize | Helm |
|---|---|---|
| Config format | Plain YAML + patches | Go templates + values.yaml |
| Versioning | Git history is the version | Helm release history + chart semver |
| Sharing/packaging | Limited (no OCI distribution) | First-class (OCI, Helm repos) |
| Rollback | git revert | helm rollback |
| Conditionals | JSON patch or separate overlay | {{- if .Values.foo }} |
| Learning curve | Low (know YAML, know Kustomize) | Higher (Go template syntax) |
| Third-party charts | Poor (no dependencies) | Excellent (bitnami, prometheus-community) |
| Best for | In-house apps, simple env overlays | Third-party software, complex parameterisation |
Common pattern: use Helm for third-party infrastructure (cert-manager, Prometheus, NGINX ingress) and Kustomize for in-house application deployments.
Argo CD with Kustomize
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payments-api
namespace: argocd
spec:
source:
repoURL: https://github.com/acme/k8s-config
targetRevision: main
path: services/payments-api/overlays/production
kustomize:
images:
- ghcr.io/acme/payments-api:sha-abc123
# Optional: pass additional patches at Argo CD level
patches:
- target:
kind: Deployment
name: payments-api
patch: |-
- op: replace
path: /spec/replicas
value: 5
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
Related
- Helm — Helm charts, comparison and when to choose
- CI/CD Pipelines — automating image tag updates
- GitOps (08) — Argo CD integration