Kustomize
Kustomize is the "no templates" way to manage Kubernetes manifests. You write plain YAML for a base, then overlays patch it per environment. Built into kubectl (`kubectl apply -k`), works alongside Helm, and avoids the "two-week onboarding into a templating language" cost of large Helm charts.
Base + overlays + patches + secret generators
EXAMPLE
# Layout
# k8s/
# ├── base/
# │ ├── kustomization.yaml
# │ ├── deployment.yaml
# │ ├── service.yaml
# │ └── ingress.yaml
# └── overlays/
# ├── staging/
# │ ├── kustomization.yaml
# │ └── patches/
# │ └── replicas.yaml
# └── production/
# ├── kustomization.yaml
# ├── patches/
# │ ├── replicas.yaml
# │ └── resources.yaml
# └── ingress-host.yaml
# ===== base/deployment.yaml =====
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
labels: { app: shop-api }
spec:
replicas: 1
selector: { matchLabels: { app: shop-api } }
template:
metadata: { labels: { app: shop-api } }
spec:
containers:
- name: api
image: example.com/shop-api:0.0.0 # overlays set the real tag
ports: [{ containerPort: 8080 }]
env:
- { name: NODE_ENV, value: development }
resources:
requests: { cpu: '100m', memory: '256Mi' }
limits: { cpu: '500m', memory: '512Mi' }
# ===== base/service.yaml =====
apiVersion: v1
kind: Service
metadata: { name: shop-api }
spec:
selector: { app: shop-api }
ports: [{ port: 80, targetPort: 8080 }]
# ===== base/kustomization.yaml =====
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: shop
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
commonLabels:
app: shop-api
images:
- name: example.com/shop-api
newTag: latest
# ===== overlays/staging/kustomization.yaml =====
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: shop-staging
resources:
- ../../base
images:
- name: example.com/shop-api
newTag: 'staging-1.4.0'
patches:
- path: patches/replicas.yaml
configMapGenerator:
- name: shop-config
literals:
- NODE_ENV=staging
- API_BASE=https://api.staging.example.com
secretGenerator:
- name: shop-secrets
envs: [ .env.staging ] # .env.staging is gitignored
generatorOptions:
disableNameSuffixHash: false # name gets a hash so new contents trigger rolling update
# patches/replicas.yaml — patch only specific fields
apiVersion: apps/v1
kind: Deployment
metadata: { name: shop-api }
spec:
replicas: 3
# ===== overlays/production/kustomization.yaml =====
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: shop-prod
resources:
- ../../base
images:
- name: example.com/shop-api
newTag: '1.4.0'
patches:
- path: patches/replicas.yaml
- path: patches/resources.yaml
- target: { kind: Ingress, name: shop-api }
path: ingress-host.yaml
configMapGenerator:
- name: shop-config
literals:
- NODE_ENV=production
- API_BASE=https://api.example.com
# patches/resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata: { name: shop-api }
spec:
template:
spec:
containers:
- name: api
resources:
requests: { cpu: '200m', memory: '512Mi' }
limits: { cpu: '1000m', memory: '1Gi' }
# ===== Use it =====
# Render to stdout
kubectl kustomize k8s/overlays/staging
# Apply (kubectl 1.21+ has kustomize built in)
kubectl apply -k k8s/overlays/staging
kubectl apply -k k8s/overlays/production
# Diff before apply
kubectl diff -k k8s/overlays/production
# Delete
kubectl delete -k k8s/overlays/staging
# ===== Patterns to internalise =====
# - Base is the cluster-agnostic shape; overlays customise
# - Generators (configMap, secret) hash the contents into the name -> rolling
# update happens when the content changes
# - 'images:' is the cleanest way to set the tag per environment
# - patches override fields surgically; replace fields entirely with JSON 6902
# patches when needed
# - Use components for reusable bits (e.g. 'add-istio-sidecar')
# ===== Pitfalls =====
# - Editing the base from an overlay (do not — patch in the overlay)
# - Committing secrets to overlays (use envs: + gitignore the env file)
# - Forgetting to bump 'newTag' on deploy (overlays continue to point at old image)
# - Mixing kustomize with Helm in a way the team finds confusing
# -> pick one: kustomize for your services, Helm for off-the-shelf charts
# ===== When to pick kustomize vs helm =====
# Kustomize: your own services where YAML is the cleanest expression
# Helm: off-the-shelf 3rd-party services (Redis, Cert-Manager, NGINX)
# Combined: use Helm to install the operator, kustomize to manage your CRs
# ===== Tooling =====
# kustomize edit set image example.com/shop-api=:1.4.1 # CLI mutation
# kustomize build --enable-helm # mix Helm subcharts
# Skaffold / ArgoCD support kustomize natively
Why it matters
Pair kustomize for your services with Helm for off-the-shelf dependencies. Helm wins where you want a vendor-maintained chart with rich values; kustomize wins for the manifests you own, because the diff between staging and production is a tiny overlay you can read in seconds — no templating language to learn.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# Base + overlays. Same YAML, env-specific patches. kubectl apply -k overlays/prodTry it Yourself »
Discussion
Loading…