Helm vs Kustomize
Key Differences in Kubernetes
Helm is a package manager that uses Go templates to generate Kubernetes manifests from parameterized charts with values files. Kustomize is a template-free tool that patches and overlays plain Kubernetes YAML files to customize them for different environments. Helm excels at packaging and distributing reusable applications; Kustomize excels at customizing existing manifests without templating.
Side-by-Side Comparison
| Dimension | Helm | Kustomize |
|---|---|---|
| Approach | Templating — Go templates with {{ .Values.x }} placeholders | Patching — overlays and patches applied to base YAML files |
| Packaging | Charts are packaged, versioned, and distributed via repositories | No packaging — works with plain YAML files in directories |
| Reusability | Charts are designed to be shared and reused across teams | Bases can be referenced but are not packaged for distribution |
| Complexity | More complex — template syntax, helpers, hooks, dependencies | Simpler — standard YAML with strategic merge patches |
| Built-in to kubectl | Requires separate Helm CLI installation | Built into kubectl via kubectl apply -k |
| Release Management | Tracks releases, supports rollback via helm rollback | No release tracking — relies on kubectl and GitOps |
| Dependencies | Supports chart dependencies (subcharts) | No dependency management — compose manually |
| Learning Curve | Steeper — Go template syntax, chart structure, Helm concepts | Gentler — plain YAML with kustomization.yaml |
Detailed Breakdown
Helm — Templating Approach
Helm uses a chart structure with Go templates:
my-app/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
ingress.yaml
_helpers.tpl
The template files contain Go template directives:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.containerPort }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
# values.yaml
replicaCount: 2
image:
repository: my-app
tag: "1.0.0"
containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
You install with:
# Default values
helm install my-release ./my-app
# Override for production
helm install my-release ./my-app -f values-prod.yaml --set replicaCount=5
Kustomize — Patching Approach
Kustomize works with plain YAML files organized in base and overlay directories:
my-app/
base/
kustomization.yaml
deployment.yaml
service.yaml
overlays/
dev/
kustomization.yaml
replica-patch.yaml
prod/
kustomization.yaml
replica-patch.yaml
resources-patch.yaml
The base contains standard, unmodified Kubernetes YAML:
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:1.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
Overlays patch the base for each environment:
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
namespace: production
patches:
- path: replica-patch.yaml
- path: resources-patch.yaml
# overlays/prod/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5
# overlays/prod/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: my-app
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
Apply with:
# Preview the output
kubectl kustomize overlays/prod
# Apply directly
kubectl apply -k overlays/prod
Key Differences in Practice
Readability: Kustomize bases are valid Kubernetes YAML that you can kubectl apply directly. Helm templates are not valid YAML until rendered — the {{ }} syntax makes them harder to read and validate.
Debugging: With Kustomize, you can read the base file and mentally apply the patches. With Helm, you run helm template to see the rendered output, which can be surprising when template logic is complex.
Ecosystem: Helm has a massive chart ecosystem. Need PostgreSQL? helm install postgresql bitnami/postgresql. Need Redis? helm install redis bitnami/redis. Kustomize has no equivalent repository — you reference upstream YAML files or copy them.
Release Management
Helm tracks every installation as a release:
# List releases
helm list
# View release history
helm history my-release
# Rollback to a previous revision
helm rollback my-release 2
# Upgrade with new values
helm upgrade my-release ./my-app -f values-prod.yaml
Kustomize has no concept of releases. It generates YAML and applies it — the state tracking is in the Kubernetes cluster itself (or in a GitOps tool like Argo CD or Flux).
Helm Hooks
Helm supports lifecycle hooks that Kustomize cannot replicate:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: migrate
image: my-app:1.0.0
command: ["./migrate.sh"]
restartPolicy: Never
This Job runs before every helm upgrade, ensuring database migrations happen before the new version is deployed.
Using Both Together
Many teams combine Helm and Kustomize:
- Use Helm to install third-party charts (databases, monitoring, ingress controllers)
- Use Kustomize to manage their own application manifests with environment overlays
- Use
helm templateto render Helm charts into plain YAML, then use Kustomize to apply additional patches
# Render Helm chart and pipe to Kustomize
helm template my-release bitnami/postgresql -f values.yaml > base/postgresql.yaml
This hybrid approach gives you the best of both worlds — the rich ecosystem of Helm charts and the simplicity of Kustomize patches for your own code.
GitOps Integration
Both tools integrate with GitOps platforms, but Kustomize is often preferred because its output is deterministic and easy to diff in pull requests. Argo CD and Flux support both Helm charts and Kustomize overlays natively.
Use Helm when...
- •You're distributing an application for others to install (like a public chart)
- •You need release management with upgrade and rollback tracking
- •Your application has complex parameterization with many configurable values
- •You want to leverage the ecosystem of existing charts (PostgreSQL, Redis, NGINX)
- •You need lifecycle hooks (pre-install, post-upgrade jobs)
Use Kustomize when...
- •You want to customize existing manifests without adding template syntax
- •You're managing environment-specific variations (dev, staging, prod)
- •You want to keep your YAML valid and readable without template markers
- •You're using GitOps and want plain YAML that is easy to diff and review
- •You need simple overlays on top of upstream manifests
Model Interview Answer
“Helm and Kustomize take fundamentally different approaches to managing Kubernetes manifests. Helm is a package manager — it uses Go templates to generate YAML from parameterized charts. You define defaults in values.yaml and override them per environment. Helm also tracks releases and supports rollbacks. Kustomize uses a template-free approach — you write plain Kubernetes YAML as a base and apply overlays and patches for different environments. Kustomize is built into kubectl and keeps manifests readable. Many teams use both: Helm charts for third-party applications (databases, monitoring) and Kustomize for their own application manifests where full templating is unnecessary.”