Learn the key differences between Helm and Kustomize and how to combine them for flexible Kubernetes deployments
Kustomize vs Helm: Do You Really Have to Choose?
When teams start working with Kubernetes, two tools often come up: Helm and Kustomize. Both help manage your YAML files. Both aim to simplify Kubernetes deployments. Because of this overlap, many people think they do the same thing.
Some even believe you must pick one — kustomize vs helm. Fortunately, you don’t have to do that. In fact, these tools complement each other and can give you more flexibility and control.
In this article, I’ll help you understand what each tool is good at and how they work together. You’ll also see a step-by-step example of how to configure the Prometheus Helm chart using Kustomize.
Let’s get started!
Understanding Each Tool’s Purpose
Before we jump into the practical example, let’s briefly review what each tool does.
What Is Helm?
Helm acts as a package manager for Kubernetes. It bundles applications into charts. These charts include everything needed to run an application: deployments, services, ingresses, config maps, and more. Helm handles versioning, dependencies, and complex installations.
For instance, when you install a database like PostgreSQL using Helm, you’re getting a well-tested configuration that thousands of other developers have used. The chart maintainers have thought about security, performance, and operational concerns, so you don’t have to start from scratch.
Helm uses Go templates to generate Kubernetes manifests. Below is a typical Helm chart structure:
# Chart.yaml - Chart metadata for an app called my-web-app
apiVersion: v2
name: my-web-app
version: 1.2.0
description: A simple web application
dependencies:
- name: redis
version: 10.5.7
repository: https://charts.bitnami.com/bitnami
# values.yaml - Default configuration values
image:
repository: nginx
tag: "1.21"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: chart-example.local
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
# templates/deployment.yaml - Template file
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-web-app.fullname" . }}
labels:
{{- include "my-web-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-web-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-web-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
resources:
{{- toYaml .Values.resources | nindent 10 }}
When you run helm install my-app ./my-web-app, Helm processes these templates and generates the final Kubernetes manifests. The templating engine replaces all the {{ .Values.* }} placeholders with actual values from your values.yaml file.
What Is Kustomize?
Kustomize works with plain YAML. It doesn’t use templates. It lets you patch, override, or combine base configurations in different ways. This makes debugging easier because there’s no intermediate templating step.
The main idea is that you:
Define a base configuration.
Apply overlays for environment-specific changes like dev, staging, or prod.
Let’s say you have a base Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app
labels:
app: my-web-app
spec:
replicas: 2
selector:
matchLabels:
app: my-web-app
template:
metadata:
labels:
app: my-web-app
spec:
containers:
- name: web
image: nginx:1.21
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
In the base folder, you also need a kustomization.yaml:
resources:
- deployment.yaml
Kustomize needs the kustomization.yaml to know what files belong to the base setup.
Now let’s say you want to increase the resource limits in the development environment. You can create a patch with the specific settings:
# overlays/development/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app
spec:
template:
spec:
containers:
- name: web
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
And reference this patch in the dev overlay config:
# overlays/development/kustomization.yaml - dev kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base # the base config's location
patches: # the patch to apply
- path: development-patch.yaml
target:
kind: Deployment # this will be applied to the deployment.yaml manifest
name: my-web-app
You can preview the final patched manifest:
kustomize build overlays/development
You can also apply it to the cluster using kubectl:
kubectl apply -k overlays/development
When to Combine Kustomize and Helm
Now let’s explore some common scenarios where combining these tools makes sense.
Scenario 1: Multi-Environment Application Deployment
Imagine you’re deploying a microservices application across development, staging, and production environments. Each environment requires unique resource limits, ingress rules, secrets, and scaling policies.
Using Helm alone would require multiple values files that become hard to maintain. You’d end up with complex conditional logic in your templates. Using Kustomize alone means you can’t easily share your application configuration with other teams.
The Combined Solution:
Package your application as a Helm chart for easy distribution.
Use Kustomize overlays to handle environment-specific customizations.
Scenario 2: Third-Party Application Customization
Let’s say you need to deploy Prometheus for monitoring. The official Prometheus Helm chart has hundreds of configuration options. However, you need some specific customizations that aren’t directly supported by the chart’s values.
The Combined Solution:
Use the official Prometheus Helm chart as your base.
Apply Kustomize patches for your specific requirements.
Scenario 3: Compliance and Security Overlays
In regulated industries, you often need to apply consistent security policies across all applications. These might include security contexts, network policies, or resource quotas that must be applied uniformly.
The Combined Solution:
Package applications normally with Helm.
Use Kustomize to apply organization-wide security policies.
How Both Tools Work Together
Let’s walk through exactly what happens when you combine these tools, because understanding the process helps you see the benefits.
Step 1: Helm Processing: When you run helm template, Helm takes your chart templates and values, processes them through the Go template engine, and outputs standard Kubernetes YAML manifests. These manifests are complete and ready to apply to your cluster.
Step 2: Kustomize Processing: Kustomize takes these Helm-generated manifests as base resources. It applies your overlays and patches, creating the final customized manifests. Kustomize doesn’t care that these base manifests came from Helm — it just sees standard Kubernetes YAML.
Step 3: Deployment: The final manifests are applied to your Kubernetes cluster.
Optional: Use Kustomize with Helm’s Post-Renderer
When you apply Kustomize outside of Helm, using something like:
helm template myapp ./mychart > rendered.yaml
kustomize build overlays/dev | kubectl apply -f -
You lose Helm features like helm upgrade, release tracking, and rollback.
If you want to keep using Helm to install and upgrade your app, but still patch the rendered manifests with Kustomize, you can use the --post-renderer flag (available since Helm 3.1+).
This lets Helm do all the templating and release management. But before applying the manifests to the cluster, Helm passes them to Kustomize. Kustomize applies your patches, and then Helm proceeds with the deployment.
For example, the command would look like this:
helm upgrade --install myapp ./mychart --post-renderer ./kustomize-wrapper.sh
You can read more about this advanced Helm feature in the official docs.
Hands-on Example: Customize a Prometheus Helm Chart with Kustomize
Feeling ready to try Helm and Kustomize together?
In this demo, you’ll install Prometheus with Helm, then use Kustomize to:
Add a custom label to all resources.
Patch the prometheus-server service to change its type to LoadBalancer.
Create this folder structure:
mkdir -p helm-kustomize-demo/base
mkdir -p helm-kustomize-demo/overlays/dev
cd helm-kustomize-demo
Generate Prometheus base using Helm:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm template prometheus prometheus-community/prometheus \
--values prometheus-values.yaml > base/prometheus.yaml
Create an empty prometheus-values.yaml because we won’t need it in this demo.
Explanation:
helm template: Renders the chart templates to raw YAML (instead of installing them directly).
prometheus: The name of the Helm release (can be anything).
prometheus-community/prometheus: The name of the chart from the Helm repo.
--values prometheus-values.yaml: Custom values file to override chart defaults.
base/prometheus.yaml: Output the raw Kubernetes manifests to a file (no deployment yet).
So you’re extracting the Helm chart into raw YAML and saving it in your Kustomize base directory.
Create the base/prometheus/kustomization.yamlfile:
resources:
- prometheus.yaml
Add this Kustomize patch to add a label called dev to overlays/dev/label-patch.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus-server
labels:
env: dev
Create this patch to change the service type in overlays/dev/service-patch.yaml :
apiVersion: v1
kind: Service
metadata:
name: prometheus-server
spec:
type: LoadBalancer
This patch changes the service type from ClusterIP to LoadBalancer. Here’s a snippet from the Prometheus Helm chart’s service template:
# Source: prometheus/charts/alertmanager/templates/services.yaml
apiVersion: v1
kind: Service
metadata:
name: prometheus-alertmanager
labels:
helm.sh/chart: alertmanager-1.23.0
app.kubernetes.io/name: alertmanager
app.kubernetes.io/instance: prometheus
app.kubernetes.io/version: "v0.28.1"
app.kubernetes.io/managed-by: Helm
namespace: default
spec:
type: ClusterIP
# code omitted for brevity....
Create the Kustomize file for the dev enviroment — overlays/dev/kustomization.yaml :
resources:
- ../../base/prometheus
patches:
- path: label-patch.yaml
target:
kind: Deployment
name: prometheus-server
- path: service-patch.yaml
target:
kind: Service
name: prometheus-server
You should end up with the following folder tree:
helm-kustomize-demo/
├─ base/
│ ├─ prometheus.yaml
│ └─ kustomization.yaml
└─ overlays/
└─ dev/
├─ kustomization.yaml
├─ label-patch.yaml
└─ service-patch.yaml
Deploy it to the Kubernetes cluster:
kustomize build overlays/dev | kubectl apply -f -
Explanation:
kustomize build : Reads the base YAML and applies patches defined in the custom overlay.
| kubectl apply -f -: Sends the result directly to kubectl for deployment.
Test the configuration:
Run kubectl get svc to check if prometheus-server is of type LoadBalancer.
Run kubectl get deploy prometheus-server -o yaml | grep env to check for the custom label.
Conclusion
In this article, you saw the differences between Helm and Kustomize and how they can work together. You can select the best option for your needs based on your specific use case.
You also saw a code example of how to combine Kustomize and Helm to deploy Prometheus.
Let’s do a quick recap of each tool’s benefits:
Helm Benefits:
Package distribution and sharing across teams.
Semantic versioning for application releases.
Dependency resolution between related services.
Complex application bundling with hooks and tests.
Large ecosystem of community-maintained charts.
Kustomize Benefits:
Template-free configuration that’s easier to debug.
Environment-specific customization without duplication.
Patch-based modifications that are more maintainable (you can see all changes in version control).
Native kubectl integration (Since Kubernetes 1.14!).
Better support for GitOps workflows (because of its declarative configuration management directly with Kubernetes manifests).
I hope this article has been useful, and now you have a clear vision of both tools.
Want to go deeper with Helm? I wrote a practical ebook for beginners, available as a downloadable PDF on Gumroad. It teaches you the fundamentals with practical examples and short lessons. You can also visit my Helm course for beginners.
Want to go deeper with Helm? I wrote a practical ebook for beginners, available as a downloadable PDF on Gumroad. It teaches you the fundamentals with practical examples and short lessons. You can also visit my Helm course for beginners.
You can find the example code for this article in my GitHub repo.
Thank you for reading, and see you next time!
This story was originally published on https://medium.com/curious-devs-corner/helm-vs-kustomize-comparison-use-cases-how-to-combine-them-ffeae155c3df

Top comments (0)