Use Kustomize (Without Templates)
Manage Kubernetes manifests without templates using Kustomize bases, overlays, patches, and generators — applied with plain kubectl -k.
Before you start
- ▸A working Kubernetes cluster (local kind/minikube or remote) with kubectl configured
- ▸kubectl 1.14 or later installed and pointed at your cluster
- ▸Basic familiarity with Kubernetes Deployments and Services
- ▸kustomize CLI installed (standalone binary recommended over the kubectl-embedded version)
Kustomize lets you manage Kubernetes manifests through plain YAML layering — no templating engine, no new syntax to learn. It ships inside kubectl (since 1.14) and is also available as a standalone binary. The core idea: write a reusable base, then apply environment-specific overlays on top without touching the originals. Patches, name prefixes, config generators — all composable, all plain YAML.
How Kustomize Thinks
Every Kustomize directory needs a kustomization.yaml file. That file declares what resources to include, what patches to apply, and what generators to run. kubectl -k reads this file, assembles the final manifest in memory, and streams it to the cluster — nothing is written to disk unless you redirect output.
- Base: canonical, environment-agnostic manifests shared across all environments.
- Overlay: a directory that points at a base and layers differences on top of it.
- Patch: a fragment of YAML (strategic merge) or a JSON 6902 patch that modifies a specific resource.
- Generator: a built-in instruction to produce a ConfigMap or Secret from files or literals.
Install and Verify
kubectl embeds a version of Kustomize, but it often lags behind. For serious use, install the standalone binary to get the current release.
Standalone binary (all distros)
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
Debian / Ubuntu (via apt)
sudo apt install -y kustomize
Fedora / RHEL family
sudo dnf install -y kustomize
Arch
sudo pacman -S kustomize
Confirm the version:
kustomize version
Build a Base
Create a directory layout for a small web application. The base holds the Deployment and Service that every environment shares.
mkdir -p myapp/base myapp/overlays/dev myapp/overlays/prod
Write the Deployment:
cat > myapp/base/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx:1.27
ports:
- containerPort: 80
EOF
Write the Service:
cat > myapp/base/service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 80
EOF
Register both files in the base kustomization.yaml:
cat > myapp/base/kustomization.yaml <<'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
EOF
Preview the base output — nothing is applied yet:
kustomize build myapp/base
Create Overlays
Overlays reference the base via a relative path and stack their own customizations on top. Start with the dev overlay.
Dev overlay
cat > myapp/overlays/dev/kustomization.yaml <<'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: dev-
commonLabels:
env: dev
resources:
- ../../base
EOF
Preview — every resource name will be prefixed with dev-:
kustomize build myapp/overlays/dev
Prod overlay with a replica patch
Production needs more replicas. Write a strategic merge patch — only the fields you care about, merged by Kubernetes field-matching rules:
cat > myapp/overlays/prod/replica-patch.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
EOF
cat > myapp/overlays/prod/kustomization.yaml <<'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: prod-
commonLabels:
env: prod
resources:
- ../../base
patches:
- path: replica-patch.yaml
EOF
Patches in Detail
Kustomize supports two patch styles. Use strategic merge patches for most changes; use JSON 6902 patches when you need surgical array manipulation (adding or removing a specific list element).
JSON 6902 example: change the container image
cat > myapp/overlays/prod/image-patch.yaml <<'EOF'
- op: replace
path: /spec/template/spec/containers/0/image
value: nginx:1.27-alpine
EOF
Reference it in kustomization.yaml with explicit target coordinates:
cat >> myapp/overlays/prod/kustomization.yaml <<'EOF'
- path: image-patch.yaml
target:
group: apps
version: v1
kind: Deployment
name: myapp
EOF
A cleaner, Kustomize-native alternative for image replacement is the images field — no patch file needed:
# add this block to kustomization.yaml instead of a JSON patch
images:
- name: nginx
newTag: "1.27-alpine"
ConfigMap and Secret Generators
Generators create ConfigMaps or Secrets from literals or files and, crucially, append a content hash to the resource name. When the data changes, the hash changes, which forces a rolling update automatically.
ConfigMap from a file
echo "LOG_LEVEL=info" > myapp/overlays/dev/app.env
cat >> myapp/overlays/dev/kustomization.yaml <<'EOF'
configMapGenerator:
- name: myapp-config
envs:
- app.env
EOF
Build and inspect — the ConfigMap name will include a hash suffix like dev-myapp-config-6ft8km9b5t. Kustomize automatically rewrites all references to that ConfigMap inside the same build so your Deployment picks up the new name.
Secret generator
cat >> myapp/overlays/prod/kustomization.yaml <<'EOF'
secretGenerator:
- name: myapp-tls
files:
- tls.crt
- tls.key
type: kubernetes.io/tls
EOF
Never commit real secret files. Use a secrets management solution (Sealed Secrets, External Secrets Operator) and only commit the generator stub.
Apply with kubectl -k
Once you are satisfied with the built output, apply an overlay directly:
# dry-run first
kubectl apply -k myapp/overlays/prod --dry-run=server
# real apply
kubectl apply -k myapp/overlays/prod
To see exactly what will be sent to the API server before applying:
kustomize build myapp/overlays/prod | kubectl apply --dry-run=server -f -
Verify
# check resources were created with the expected prefix and labels
kubectl get all -l env=prod
# confirm replica count
kubectl get deployment prod-myapp -o jsonpath='{.spec.replicas}'
Output will show 5 for the replica count if your patch applied correctly.
Troubleshooting
- "no such file or directory" on build: All paths inside
kustomization.yamlare relative to that file's directory. Double-check relative paths inresourcesandpatches. - Patch not matching: Strategic merge patches match by
kind+metadata.name. If you have anamePrefixset in the overlay, the patch must use the base name (without the prefix); Kustomize applies the prefix after merging. - ConfigMap hash changing unexpectedly: Add
generatorOptions: {disableNameSuffixHash: true}to disable the hash if you need a stable name — but you lose the automatic rollout trigger. - kubectl version mismatch: The embedded Kustomize in
kubectlmay not support newer fields. Use the standalonekustomize build | kubectl apply -f -pipeline when in doubt. - "accumulating resources" errors: A resource listed in
resourcescannot also be a patch target in the same layer. Move the resource to the base or restructure the overlay.
Frequently asked questions
- Does Kustomize replace Helm?
- They solve different problems. Helm packages applications for distribution with a full templating engine; Kustomize layers plain YAML for operational environment management. Many teams use both: Helm to install a chart, Kustomize overlays to customize the output for each cluster.
- Can I use Kustomize with GitOps tools like Flux or Argo CD?
- Yes. Both Flux and Argo CD have native Kustomize support. Point them at an overlay directory and they will run the equivalent of kustomize build and apply the result on every reconciliation.
- Why does my ConfigMap get a random-looking suffix on its name?
- That suffix is a content hash. When the ConfigMap's data changes, the hash changes, the name changes, and Kubernetes triggers a new rollout of any workload referencing it. Set disableNameSuffixHash: true under generatorOptions if you need a stable name.
- How do I share a patch across multiple overlays without duplicating it?
- Place the patch file in the base or in a shared directory, then reference it with a relative path from each overlay's kustomization.yaml. Alternatively, create an intermediate base that includes the common patch and have each overlay reference that intermediate base.
- Is there a way to validate the built manifest before applying?
- Yes. Pipe the build output through kubeval or kubeconformist for schema validation, or use kubectl apply --dry-run=server -f - which validates against the live API server and catches admission webhook issues too.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.