Blog · August 12, 2025 · Updated on September 7, 2026 · 13 min read

Kustomize vs Helm: When to Use Which?

Multiple transparent glass panels lined up in a row forming a cohesive image with a teal glow
Photo: Noah D Wilke / Pexels

The short answer to Kustomize vs Helm: use Kustomize when you want to adjust your own manifests for several environments while staying with plain YAML. Use Helm when you want to ship an application as a package, version it, give it dependencies, or install finished third-party software. In most of the clusters I run, both work side by side, and that is not a compromise, it is the right division of labor.

The choice goes wrong mainly when a team picks one tool for a job the other was built for: a chart with dozens of placeholders for two environments of the same app, or a pile of overlays for software that is supposed to ship to other teams as a package.

I run Kubernetes clusters in production and wrote the Kubernetes Practical Guide for Rheinwerk Computing (2024). I covered Kustomize in the chapter on everything as code and Helm in a chapter of its own. Here is the short version with the decision guide I would have wanted for myself back then. You can find all my Kubernetes articles collected on the Kubernetes page.

The problem both tools solve: copied manifests

Almost every team ends up in the same spot sooner or later. The Deployment for the development environment needs one replica and an image with the latest build, production needs three replicas, an approved image, higher resource requests and different labels. The first instinct is to copy the file: deployment-dev.yaml and deployment-prod.yaml.

I have seen this at several companies, and it always plays out the same way. A developer adds an environment variable to one file and forgets the other. After three months nobody knows anymore which differences between the files are intentional and which are mistakes. Manifests have to be parameterized instead, and with an established tool, not a homegrown sed script in the pipeline.

This is exactly where the paths split. Kustomize and Helm solve the same problem with two fundamentally different approaches, and almost everything you need to know for the decision follows from that difference. If you are still missing the basics on manifests, read Kubernetes YAML: Understanding Manifests first.

Kustomize vs Helm: overlays against templates

Kustomize changes finished manifests. You write a base out of completely normal YAML, the kind you could roll out with kubectl apply -f at any time, and place an overlay next to it per environment that only contains the fields that differ. Kustomize layers both and prints the result. There are no placeholders, no template language, and nothing you first have to learn. Since Kustomize is built into kubectl, you do not need an extra program either.

Helm generates manifests. A Helm chart contains templates written in the Go template language, holding values like {{ .Values.replicaCount }}. Only at render time does Helm plug in values from one or more values.yaml files and turn the templates into valid manifests. The templates themselves are no longer valid YAML, but in exchange they can hold conditions, loops and helper functions.

On top of that, Helm is more than a templating tool. It is a package manager, comparable to Homebrew on a Mac: you pick a package, say where it comes from, and install it into the cluster. Kustomize has no package layer like that, and does not want one.

In one sentence: Kustomize adjusts, Helm packages. Keep that sentence in mind and you will get the decision right most of the time.

Kustomize patches: base and overlays in practice

A Kustomize project consists of one base and any number of overlays. The base holds the manifests you would write anyway, plus a kustomization.yaml that lists them. For a Deployment web with a Service, the base looks like this:

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

The overlay for production points at the base and brings along a patch. The patch is an incomplete manifest that only contains the fields that should change. The target tells Kustomize which resource the patch applies to:

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
namePrefix: prod-
commonAnnotations:
  owner: platform-team
patches:
  - path: replicas.yaml
    target:
      kind: Deployment
      name: web
# overlays/prod/replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: web
          image: registry.example.com/web:stable
          resources:
            requests:
              cpu: 100m
              memory: 128Mi

With kubectl kustomize overlays/prod you see the finished result on the console, with kubectl apply -k overlays/prod you roll it out. The Deployment is now called prod-web, carries the annotation, has three replicas and the resource requests, and the Service is unchanged apart from the prefix and annotation. This preview is one of Kustomize's biggest advantages: the result is ordinary YAML you can check with any validator and read in code review.

This form is called a strategic merge patch, and it covers nine out of ten cases in my projects. For fields in lists that cannot be targeted cleanly that way, there are also JSON patches per RFC 6902, where you specify an operation and a path:

patches:
  - target:
      kind: Deployment
      name: web
    patch: |-
      - op: replace
        path: /spec/replicas
        value: 3

Beyond patches, Kustomize also ships built-in generators and transformers. The configMapGenerator builds a ConfigMap directly from existing configuration files, instead of you copying their content into a manifest, and appends a hash to the name, so a changed content automatically triggers a new rollout. You have already seen commonAnnotations, namePrefix and nameSuffix above; with those you satisfy compliance requirements, such as a cost center on every resource, without maintaining each one by hand. The full list is in the Kustomize reference on kubernetes.io.

Helm: chart, release and values

With Helm you need to know three terms. A chart is the package: all the templates and default values an application needs to run. A release is an installed instance of a chart in the cluster, and the same chart can run several times as separate releases. A repository is where charts are stored and distributed, similar to a container registry for images.

Adapting to environments happens through values. Every chart ships a values.yaml with defaults, and you only override what differs:

# values.yaml (defaults shipped in the chart)
replicaCount: 1
image:
  repository: registry.example.com/web
  tag: latest
ingress:
  enabled: false
# values-prod.yaml
replicaCount: 3
image:
  tag: stable
ingress:
  enabled: true
  host: shop.example.com

You roll it out with helm upgrade --install web ./charts/web -f values-prod.yaml. The --install flag means the same command installs the first time and updates afterward, so a pipeline does not have to distinguish whether the release already exists. The chart's own values.yaml always stays the base, every file you pass with -f overrides it, and with several files the last one wins. In my setup, company-wide defaults, environment values and cluster specifics accordingly live in separate files.

In the template you can then say, for example, that the Ingress is only created when it is switched on. With Kustomize the same thing only works by putting the Ingress file exclusively into the prod overlay:

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}
spec:
  rules:
    - host: {{ .Values.ingress.host }}
{{- end }}

Helm also remembers every revision of a release. helm history web shows them, helm rollback web 3 restores an earlier one, and helm uninstall web removes every resource belonging to the release in one go. This bookkeeping in the cluster is both the cost and the benefit of Helm: more convenience, but also state outside Git that you have to be aware of. How a chart is structured in detail is covered in Helm Charts Explained: Packages for K8s.

When to use Kustomize, when Helm: the decision table

The table below sums up how I decide on projects. It is a rule of thumb, not a standard, and in borderline cases the tool your team already knows wins. The first row is the fastest decision aid in practice: third-party software almost always arrives as a chart, and your own manifests almost always get by without templates.

Situation Kustomize Helm
Third-party software or your own application? your own application, whose manifests you write yourself third-party software that ships as a chart
Own application, two to four environments first choice, plain YAML works, but more effort
Packaging software for other teams or customers not intended for this first choice, chart plus repository
Installing finished software (Prometheus, cert-manager, ArgoCD) only for patching third-party manifests afterward first choice, the projects ship charts
Optional resources per environment file only in the matching overlay condition in the template
Same app running several times in one cluster namePrefix and nameSuffix separate release per instance
Dependencies (app needs a database chart) assemble manually dependencies in Chart.yaml
Rollback via Git history release revisions and Git
Checking the result before rollout kubectl kustomize returns YAML helm template returns YAML
Learning curve flat, just YAML and patches Go templates and helper functions
State in the cluster none, just the resources release secrets with history

Three patterns come up most often for me. A team with its own application and the environments dev, staging and prod moves faster and with fewer mistakes using Kustomize, because everyone can read the result. An agency running the same software for several customers with slightly different configuration needs Helm, because a chart with values describes exactly that problem. And every cluster running Prometheus, cert-manager or ArgoCD itself uses Helm, because those projects ship their software as charts and nobody wants to maintain those manifests by hand.

An honest note on third-party charts: they are convenient, but large, hard to read, and not tailored to your case. Before going to production I look into the templates and check whether the chart is maintained. For your own application, a small, self-written chart is often better than a bloated template chart.

Kustomize vs Helm with ArgoCD: what the GitOps flow changes

Once ArgoCD enters the picture, the question shifts. ArgoCD can handle both natively: it detects a kustomization.yaml in the path and renders with Kustomize, it detects a Chart.yaml and renders with Helm. The Application resource for a Kustomize overlay is correspondingly short:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/infra.git
    targetRevision: main
    path: kustomize/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: web
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

For Helm you swap the source block and point at the values files:

  source:
    repoURL: https://github.com/example/infra.git
    targetRevision: main
    path: charts/web
    helm:
      valueFiles:
        - values.yaml
        - values-prod.yaml

The biggest pitfall I have run into myself: ArgoCD does not install Helm charts with helm install, it renders them with helm template and applies the result like ordinary manifests. So there is no Helm release in the cluster, helm ls shows nothing, and helm rollback does not work. Rollback in this case means reverting the commit in Git, and ArgoCD syncs back to the old state. Template functions that look into the cluster at runtime also return no data when rendered this way. Knowing these limits, you get along fine; not knowing them, you spend a long time hunting for the bug. The details are in the ArgoCD documentation on Helm.

That means Helm loses its biggest convenience advantage under GitOps, the release bookkeeping, and Kustomize loses its biggest drawback, the missing package format. ArgoCD already versions everything through Git, and a chart from a repository can be pulled into Kustomize through the helmCharts entry in the kustomization.yaml and then patched further. ArgoCD needs to run with the build option --enable-helm for that, described in the ArgoCD documentation on Kustomize.

This is how my clusters run today: infrastructure components come as Helm charts from the projects' own repositories, with values files in Git. My own applications get a small chart when they run several times or for several customers, and Kustomize when it is just about dev and prod of the same installation. Where a third-party chart is missing some small thing, Kustomize patches the rendered result afterward, rather than me forking the chart. I describe the full setup in GitOps with ArgoCD: Deploy from Git.

Frequently asked questions

Can I combine Kustomize and Helm?

Yes, and in practice that is the most common setup. Helm delivers the package, Kustomize adjusts the rendered result, either through helm template in the pipeline followed by kubectl apply -k, or through the helmCharts entry in the kustomization.yaml, which Kustomize evaluates with the --enable-helm option. That way you get finished charts and still end up with plain YAML as the last step.

Do I have to install Kustomize separately?

No. Kustomize is built into kubectl and runs through kubectl kustomize for the preview and kubectl apply -k for rolling out. The standalone kustomize CLI is usually newer than the version built into kubectl and offers a few features earlier; you do not need it to get started.

What exactly are Kustomize patches?

A patch is an incomplete manifest or a list of operations that Kustomize applies to a resource in the base. Strategic merge patches look like a shortened Deployment and get merged with the original. JSON patches per RFC 6902 describe individual operations like replace or add at a path and help with list elements that are otherwise hard to target.

Is Kustomize easier to learn than Helm?

For a team that can already write Kubernetes manifests, yes. Kustomize only requires the base-plus-overlay concept and a handful of fields in the kustomization.yaml. Helm additionally requires the Go template language with conditions, loops, helper files and handling whitespace in the template. The effort pays off once you build charts for others; for two environments of your own app it rarely does.

Do I still need Helm at all if I use ArgoCD?

Not necessarily for your own applications, since ArgoCD handles versioning and rollback through Git. For infrastructure software like Prometheus, cert-manager or ArgoCD itself, Helm stays the standard, because those projects maintain their manifests as charts. ArgoCD renders these charts without you needing the Helm CLI in operation.

Where to go next

If you are facing the decision right now, set up your manifests as a Kustomize base with one overlay per environment and see whether that gets you through. Once you need a package for others, or a condition in the manifest that overlays cannot represent cleanly, switch that particular application to a chart. How a chart is structured is shown in Helm Charts Explained: Packages for K8s, and how both tools work together with ArgoCD is covered in GitOps with ArgoCD: Deploy from Git.

In full detail, with the generators and the complete structure of a chart, this is covered in chapters 4 and 9 of my Kubernetes Practical Guide (Rheinwerk Computing).

Kevin Welter

Kevin Welter

Developer, IT architect, author of technical books (Kubernetes, cloud infrastructures) and speaker. Runs his business with an AI workforce of eight AI employees and shows solo business owners in his community how to hire their first AI employee.

More about AI employees

Kubernetes from the basics to a production-ready cluster

Get the book