Comparison

Helm vs Kustomize

Dev, staging and prod share almost everything and differ in a few places. Helm and Kustomize are two answers to where those differences should live, and they disagree on three points: whether YAML may stop being valid YAML, what should be left behind in the cluster, and how software should be handed to people who did not write it. Those three disagreements decide the choice, and they also decide how well the two combine.

In short — Helm renders Go templates with a values file and records each install as a release object in the cluster, with history and rollback. Kustomize reads plain manifests and layers patches, generators and components on top, writes nothing to the cluster of its own, and ships inside kubectl. Use Helm to package and distribute; use Kustomize to own and vary; combine them through helmCharts inflation or a Helm post-renderer only when you have measured what each hybrid gives up.
Helm vs Kustomize: summary by dimension
DimensionHelmKustomize
ModelGo templates plus values, rendered into manifestsPlain manifests plus patches, generators and components, no templating
Latest release (Sep 2026)v4.3.0 (Helm 3: v3.22.0 is the final minor release; security fixes only until 10 February 2027)kustomize v5.8.1 standalone; kubectl embeds its own pinned version
Unit of workChart (Chart.yaml, templates/, values.yaml) installed as a named releasekustomization.yaml pointing at resources, overlays and components
State left in the clusterRelease records stored as Secrets in the release namespace (HELM_DRIVER), history kept to 10 by defaultNone of its own; only the objects it applied
Rollbackhelm rollback RELEASE [REVISION] against stored historyRevert in Git and apply again; nothing to roll back to otherwise
EnvironmentsValues files, conditionals and named templates inside the chartOverlays per environment, patches, generators, optional components
DistributionPackaged .tgz, chart repositories, OCI registries, install by digestDirectories and remote Git refs; no packaging step
CRDscrds/ installed once and never upgraded or deleted by HelmOrdinary resources; applied and updated like everything else
Dry run and validationhelm template locally; --dry-run=server validates against the API; helm lint and values.schema.jsonkubectl kustomize to render; kubectl diff -k against the live cluster
Hybrid pathPost-renderer plugin (executables no longer accepted directly in Helm 4)helmCharts inflation generator behind --enable-helm, a deliberately limited subset
Published Jun 1, 2025·Updated ·By SecOpsLog

Where does the variation live?

Helm moves the differences out of the manifests and into values. A chart is "a collection of files inside of a directory": Chart.yaml, a values.yaml with defaults, an optional values.schema.json, and a templates/ directory that, "when combined with values, will generate valid Kubernetes manifest files". The templates are Go templates, so the source files are not manifests until rendered, and the same chart can express loops, conditionals and helper functions that a static overlay cannot.

Kustomize refuses that move on principle. Its maintainers keep a list of eschewed features, and parameterization is on it: the source YAML "gets polluted with $VARs" and can no longer be applied as is to the cluster. The base stays valid YAML you could apply unmodified; an overlay names the base under resources and adds patches, generators and transformers. The project describes its own boundary as "YAML in / YAML out" and points anyone who wants a configuration language at other tools. There are no removal directives either: an overlay can add labels, patches and resources but cannot subtract, and the documented answer to "the base has something I do not want" is to fork the base.

This is the first place the choice gets made. If the people who write the manifests and the people who deploy them are the same team, valid YAML with small patches is easier to review and harder to break. If a chart has to be handed to strangers who will install it in clusters you will never see, template plus values is the interface they expect, and values.schema.json lets you reject bad inputs before anything is rendered.

What each one leaves behind in the cluster

Helm keeps state. "By default, release information is stored in Secrets in the namespace of the release", switchable to ConfigMaps or SQL through HELM_DRIVER, and the same page notes that those records "include the contents of charts and values files, and therefore might contain sensitive data". Every upgrade adds a revision; helm upgrade keeps 10 by default (--history-max), and helm rollback takes a release name and a revision number, or with no revision "will roll back to the previous release". That history is the feature: an operator who has never seen the chart can list revisions and go back one.

Kustomize leaves nothing of its own. kubectl apply -k applies the rendered objects and that is the whole record; there is no release, no revision list, and no rollback command. The rollback story is Git: revert the overlay change and apply again. The cluster-side tooling that Kustomize does give you is comparison: kubectl diff -k shows how the live objects would change before you apply.

the same variation, expressed both ways
# Helm: values-prod.yaml, consumed by templates you cannot see from here
replicaCount: 3
image:
tag: "2.4.1"
resources:
limits:
memory: 512Mi
# Kustomize: overlays/prod/kustomization.yaml, a patch on YAML you can read
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
images:
- name: registry.example.com/shop/api
newTag: "2.4.1"
patches:
- path: replicas.yaml # replicas: 3, memory limit 512Mi
configMapGenerator:
- name: api-config
files:
- config.properties # name gets a content-hash suffix

The last three lines of the Kustomize file change behaviour, not just style. Generated ConfigMaps and Secrets "have a content hash suffix appended", so a change to config.properties produces a new object name and every Deployment that references it rolls; disable it with generatorOptions.disableNameSuffixHash if that is not what you want. A Helm chart that mounts a ConfigMap by a fixed name updates the object in place and the pods keep the old data until something restarts them, unless the chart author added a checksum annotation. Both behaviours are reasonable; only one of them happens without a chart author remembering to make it happen.

Rendering, dry runs and what a pipeline can catch

Both tools render to stdout, which is how you review them in CI. helm template renders "locally", which the docs qualify twice: "any values that would normally be looked up or retrieved in-cluster will be faked locally", and "none of the server-side testing of chart validity (e.g. whether an API is supported) is done". For a real check you pass --dry-run=server on install or upgrade, which requires cluster connectivity. kubectl kustomize renders the overlay with no cluster at all, and the rendered stream is plain objects you can hand to any validator or scanner.

render both, then compare with the cluster (representative output)
helm template shop ./chart -f values-prod.yaml --api-versions networking.k8s.io/v1 | head -n 6
---
# Source: chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
kubectl kustomize overlays/prod | grep -n "name: api-config"
12: name: api-config-8mbdf7882g
41: name: api-config-8mbdf7882g
kubectl diff -k overlays/prod
- image: registry.example.com/shop/api:2.4.0
+ image: registry.example.com/shop/api:2.4.1

Two different pictures of the same objects. Helm gives you a rendered document per template file with a source comment; Kustomize gives you the assembled objects with the generated names already substituted into every reference. If your pipeline gate is "scan what will actually be applied", both satisfy it, but the Kustomize output is the whole truth and the Helm output is the truth minus whatever the chart looks up in-cluster at install time.

Distribution: packaged charts versus Git refs

Helm has a supply chain. Charts are packaged, versioned with semver in Chart.yaml, published to repositories or, as the docs now recommend, to OCI registries with helm push; Helm 4 can install by digest, and "charts with non-matching digests are not installed". Dependencies are declared in Chart.yaml with version constraints and conditions, and library charts exist for sharing template helpers without shipping resources. All of this is what makes a chart installable by someone who has never read it.

Kustomize has Git. A resources entry can point at a remote repository path with a ref, and kubectl kustomize accepts a GitHub URL with ?ref=... directly. There is no package, no index and no version constraint solver; the pin is the Git ref you wrote, and the trust question is whether the remote repository is one you control. The load restrictor defaults to LoadRestrictionsRootOnly, so an overlay cannot read files outside its own root unless you loosen it and accept that the kustomization stops being relocatable.

Version skew is worth stating plainly because it surprises teams. Helm 4 is "assumed to be compatible with n-3 versions of Kubernetes it was compiled against", and the project maintains a release branch for the most recent minor. Kustomize ships two ways: the standalone binary (v5.8.1 at verification) and the copy embedded in kubectl, which is pinned per kubectl release and can lag the standalone tool. A kustomization.yaml that uses a new field may build with kustomize and fail with kubectl apply -k on an older client; pin one of them in CI and use it everywhere.

CRDs, server-side apply and the upgrade nobody plans for

This is the sharpest documented difference. Helm treats CRDs as a special case: files in a chart's crds/ directory are installed before templates, but "CRDs are never reinstalled" and "CRDs are never installed on upgrade or rollback". The best-practice guide is explicit that this "was an explicit decision after much community discussion due to the danger for unintentional data loss". Operationally it means a chart bump that needs a new CRD version does not apply it; you ship CRDs as their own chart or apply them out of band, and a Kustomize overlay that includes the CRD manifest simply updates it, with all the data-loss risk that Helm was avoiding on your behalf.

Helm 4 also changed how objects are written. New releases default to server-side apply; upgrades "follow the previous apply method of the release", so "all releases created by Helm 3 will default to using client-side apply after upgrading to Helm 4" unless you pass --server-side explicitly. kubectl apply -k follows whatever kubectl apply does, with --server-side available the same way. If controllers or other tools also manage fields on the same objects, this is where conflicts show up, and it is the same conflict on both sides once Helm is on SSA.

Inference, not a documented rule
Nothing in either project documents "Helm for third-party, Kustomize for your own". It is the pattern that falls out of the documented facts above: packaging, schema and digest verification exist on the Helm side; readable YAML, patch-based variation and zero cluster state exist on the Kustomize side. Treat it as a default to argue against, not a law.

Using both: the two hybrids and what each gives up

Hybrid one is Kustomize inflating a chart. The helmCharts field "has limited support for helm chart inflation", requires kustomize build --enable-helm (or kubectl kustomize --enable-helm) and a helm binary on the path, and the support policy is narrow on purpose: bug fixes, security fixes, and "additional fields that are analogous to flags passed to helm template, except for flags such as post-renderer that allow arbitrary commands to be executed". The project states it "will not add support for private repository or registry authentication". The generator renders the chart to plain objects, at which point your patches apply; Helm never creates a release, so helm ls, history and rollback do not exist for that application.

Hybrid two is Helm calling Kustomize. Post-rendering lets you "use tools like kustomize to apply configuration changes without the need to fork a public chart", keeping the Helm release and history intact. Helm 4 turned this into a plugin mechanism: "it is no longer possible to pass an executable directly" to --post-renderer; a plugin name is required. Helm 4 also sends chart hooks to the post-renderer by default (the "combined" strategy, a change from Helm 3); the "nohooks" strategy that restores Helm 3 behaviour is set through the Go SDK, not a CLI flag, and the docs point Kustomize users at it when patches target template-only resources. The cost is a plugin to install everywhere Helm runs, and a chart whose rendered output no longer matches what its author tested.

Pick the hybrid by which record you want to keep. If the application should show up in helm history and roll back with helm rollback, post-render. If the application should be one more directory in a Git tree that a GitOps controller applies, inflate. The mechanics of the inflation pattern, with the kustomization and patch files written out, are in the Kustomize and Helm together lesson.

Where each one belongs

Which tool, by situation

SituationUseWhyWhat it costs you
Installing vendor software (ingress, database operator, observability stack) that the vendor publishes as a chartHelmThe chart is the supported interface; values.schema.json and digest pinning protect you from bad inputs and swapped artifactsCRDs in crds/ are never upgraded by Helm; plan a separate CRD step for every operator upgrade
Your own services, one Git repository, three environments, one team owning both code and manifestsKustomizeThe base is reviewable YAML, overlays hold only the differences, and generated config names roll pods on changeNo release history; rollback is git revert plus apply, so the Git history has to be clean enough to revert
Publishing an application other teams or customers will install without talking to youHelmPackaging, semver, dependencies, OCI distribution and NOTES.txt exist for exactly thisYou now maintain a templating surface; every values key you expose is an interface you cannot easily remove
A vendor chart that needs org-wide changes the chart does not expose (labels, sidecars, a hardened securityContext)Helm with a post-renderer pluginKeeps the release record and rollback while applying patches after renderingA plugin to install everywhere Helm runs, chart hooks that now pass through the post-renderer too, and rendered output the chart author never tested
GitOps repository where every application must be a plain directory the controller renders, vendor charts includedKustomize with helmCharts inflationOne rendering path, one review process, patches on top of any chartNo private registry auth in the generator, --enable-helm and a helm binary wherever builds run, and no helm history for the app
Operators and cluster add-ons whose CRDs change every releaseKustomize for the CRDs, either tool for the workloadKustomize applies CRD updates as ordinary resources; Helm will not touch them after first installYou own the data-loss review Helm was refusing to automate; diff CRD schema changes before applying

The trade-offs, priced

What you buy and what it costs

ChoiceBenefitCostConsequence, who should care
Templates and values (Helm)One artifact serves any consumer; conditionals and helpers handle real variationSource files are not manifests until rendered; whitespace and template errors are found at render timeReviews happen on rendered output, not on the diff. Platform teams shipping to many consumers should care most.
Patches on plain YAML (Kustomize)Every file in Git is applicable as-is and readable without a rendererNo parameterization, no removal directives; awkward variation ends up as a forked baseSmall diffs can hide large rendered changes; the pipeline must build and scan the overlay. Application teams owning their own manifests should care most.
Release records in the cluster (Helm)History, helm rollback, and an operator can act without the sourceSecrets hold chart and values contents; history grows until --history-max trims itRead access to release Secrets is read access to values. Anyone doing RBAC reviews should care.
No cluster state (Kustomize)Nothing to migrate, back up or clean; kubectl diff -k is the whole previewNothing to roll back to except GitGit discipline becomes the recovery plan. Teams without protected branches and clean history should care.
Chart packaging and OCI distributionSemver, dependencies, digest verification, registries you already runA packaging step and a registry to operateSupply-chain controls apply to charts as to images. Security teams should care.
CRD caution (Helm) versus CRD updates (Kustomize)Helm will not destroy CRD-backed data on upgrade; Kustomize keeps CRDs currentHelm leaves CRD upgrades to you; Kustomize leaves the data-loss judgement to youSomeone has to own CRD lifecycle explicitly in either model. Operators of CRD-heavy add-ons should care.

A decision rule in three questions

First: who consumes the output? If people outside your team install it, package it as a chart; nothing on the Kustomize side replaces semver, schema validation and digest pinning for strangers. If only your team applies it, start with Kustomize and add Helm only when you hit variation that patches cannot express.

Second: which record do you want during an incident? If the answer is "helm history, then helm rollback", keep Helm in the loop even for vendor charts you patch, and use a post-renderer plugin rather than inflation. If the answer is "the Git log", inflation into a Kustomize tree is fine and simpler to reason about, provided the loss of helm history is a decision and not an accident.

Third: who owns CRDs? Neither tool will own them for you. Helm documents that it will not upgrade them; Kustomize will upgrade them without judgement. Write down which team applies CRD changes and how they are diffed before anyone chooses a tool for the workloads that depend on them. Both projects are actively released (Helm 4.3.0 in September 2026, Kustomize 5.8.1 in February 2026). Answer the three questions first; the tool usually falls out of them.

Found a technical issue on this page? Report it with the tool version you used and the behavior you saw. How resources are maintained.

Go deeper
Hands-on courses on Helm & Kustomize