Kustomize isn’t a templating language, it’s a template-free way to customize Kubernetes manifests.
Let’s say you’ve got a Helm chart that does 90% of what you need, but you want to inject a few specific, non-templated changes into the generated YAML – maybe add a sidecar container, tweak a resource limit, or inject a specific annotation that your Helm values can’t reach. This is where Kustomize shines as a post-processor.
Here’s a common scenario: you’re deploying a service using a Helm chart, and you need to add a Prometheus scrape_config annotation to the service’s deployment.
Helm Chart (simplified templates/deployment.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-myapp
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 8080
Helm Values (values.yaml):
replicaCount: 1
image:
repository: nginx
tag: latest
When you run helm template myrelease ./mychart, you get standard Kubernetes YAML. But how do you add that Prometheus annotation after Helm has done its work?
You can’t directly add it via Helm values without making your Helm chart overly complex or brittle. This is where Kustomize comes in.
The Kustomize Approach:
-
Generate Helm Output: First, you generate the raw Kubernetes manifests from your Helm chart.
helm template myrelease ./mychart > base/deployment.yamlThis
base/deployment.yamlwill contain the output from your Helm chart. -
Create a Kustomization Directory: Create a
kustomizedirectory, and within it, akustomization.yamlfile.. ├── base │ └── deployment.yaml └── kustomize └── kustomization.yaml -
Define the Kustomization: Your
kustomize/kustomization.yamlwill reference the Helm-generateddeployment.yamland define your patches.kustomize/kustomization.yaml:apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../base/deployment.yaml patchesStrategicMerge: - deployment-patch.yaml -
Create the Patch: Now, create the
kustomize/deployment-patch.yamlfile. This file contains only the changes you want to apply. Kustomize uses strategic merge patching, which is powerful for Kubernetes objects.kustomize/deployment-patch.yaml:apiVersion: apps/v1 kind: Deployment metadata: name: myrelease-myapp # Must match the name generated by Helm annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080"Notice how this patch file is minimal. It only specifies the
metadata.annotationsyou want to add. Kustomize intelligently merges this into the existingDeploymentobject. -
Apply Kustomize: Run
kustomize buildfrom within thekustomizedirectory.cd kustomize kustomize build .The output will be the
base/deployment.yamlwith your annotations merged in.apiVersion: apps/v1 kind: Deployment metadata: name: myrelease-myapp annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" # This annotation is added spec: replicas: 1 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: nginx:latest ports: - containerPort: 8080
Why this works:
Helm’s template command is purely for rendering Go templates into static YAML. Kustomize, on the other hand, takes these static YAML files as input and applies transformations (like strategic merge patching) without needing to re-interpret any templating language. It’s a clean separation of concerns: Helm for chart-level templating and value injection, Kustomize for fine-grained, template-free manifest manipulation.
This pattern is incredibly useful for integrating off-the-shelf Helm charts into environments with specific operational requirements (like custom monitoring annotations, sidecars for logging/tracing, or security-policy additions) that the original chart authors didn’t anticipate.
The next step you’ll likely encounter is managing multiple patches for different environments, which is where Kustomize’s overlays feature becomes essential.