⚡ TL;DR: Choose between Helm's templated charts and Kustomize's overlays to tame Kubernetes YAML across environments. You will know which fits your case and how both slot cleanly into a GitOps workflow.
Contents
- The copy-paste tax
- Templates vs patches
- The picture
- Helm vs Kustomize: a head-to-head
- The same app, both ways
- How they fit GitOps
- Common mistakes that cost hours
- Takeaways
- Where to go next
The copy-paste tax
You write a clean Kubernetes Deployment. It works. Then someone says "we need it in staging too," so you copy the YAML into a new folder and change the image tag, the replica count, and the host in the Ingress. Then prod arrives, and you copy it again, three replicas this time, a different resource limit, a real TLS secret. Now you have three near-identical copies of the same manifest, and the only honest description of the difference between them lives in your head.
Three months later someone adds a new environment variable to the dev copy and forgets the other two. Prod drifts. A debugging session that should take ten minutes takes an afternoon because nobody can tell which version is the source of truth. This is the copy-paste tax, and every team that runs Kubernetes pays it until they pick a packaging tool.
The two dominant answers are Helm and Kustomize. They solve the same problem, one set of manifests, many environments, in opposite ways. This article shows how each works, gives you a small example of both, and helps you decide which one (or both) belongs in your stack.
📌 Who this is for: Engineers who already write Kubernetes manifests by hand and feel the pain of maintaining one set of YAML across dev, staging, and prod. If
kubectl apply -fand a Deployment are familiar, you're ready. New to running clusters? Start with Kubernetes in Production: Beyond the Tutorial first.
Templates vs patches
Helm parameterizes your manifests with a templating language; Kustomize layers plain-YAML patches on top of plain-YAML bases. Same destination, two philosophies.
The cleanest way to feel the difference is an analogy. Think about how you'd produce three versions of a printed letter.
| In the real world | In tech |
|---|---|
| A mail-merge letter with {{name}} blanks you fill from a spreadsheet | A Helm chart with {{ .Values.x }} placeholders filled from values.yaml |
| A printed master letter with sticky-note corrections clipped on top | A Kustomize base manifest with overlay patches applied per environment |
| The spreadsheet of names and addresses | Each environment's values file (dev / staging / prod) |
| A bundle of letters mailed under one tracking number | A Helm release, a named, versioned, rollback-able install |
Helm fills in blanks; Kustomize edits a finished document.
Helm treats your YAML as a template, the chart isn't valid Kubernetes YAML on its own, it's a program that renders into YAML. Kustomize treats your YAML as data, every file is a real, applyable manifest, and overlays just describe the diffs. That single distinction drives almost every trade-off below.
The picture
Both tools sit between your source manifests and the cluster. You author once, then a per-environment input produces per-environment output, which finally lands in a namespace. Here is the shape of it:
Base manifests flow through Helm values OR Kustomize overlays into per-env output, then to the cluster.
- Author the base once: Write the Deployment, Service, and Ingress with sensible defaults. This is the single source of truth that every environment shares.
- Describe what differs per environment: With Helm, that's a values file per env. With Kustomize, that's an overlay folder with a kustomization.yaml and small patches.
- Render the final YAML:
helm templateorkustomize buildturns base + env-input into concrete, fully-resolved manifests. No placeholders left. - Apply to the matching namespace: The rendered output goes to the cluster, either directly via the CLI, or (better) committed to Git and reconciled by a GitOps controller.
Helm vs Kustomize: a head-to-head
The two tools overlap in purpose but diverge in mechanics. The table is the fast way to internalize the trade-offs before we look at code.
| Dimension | Helm | Kustomize |
|---|---|---|
| Templating | Yes, Go templates with {{ }}, conditionals, loops, functions | No, plain YAML only, diffs via strategic-merge & JSON patches |
| Releases | First-class: named, versioned installs you can roll back with one command | None, it just emits YAML; lifecycle is whatever applies it |
| Packaging & sharing | Charts are versioned, published to registries, installed by anyone | No package format; you copy/fork or reference a remote base by URL |
| Learning curve | Steeper, a template language, values precedence, chart structure | Gentle, if you know YAML and diffs, you mostly already know it |
| Tooling | Standalone binary; rich ecosystem (Artifact Hub, hooks, tests) | Built into kubectl (kubectl apply -k) plus the standalone CLI |
| When to use | Distributing reusable apps; lots of env knobs; need rollback | Your own apps; small, readable per-env diffs; want zero magic |
Same goal, different machinery.
The same app, both ways
Imagine a small web app with a Deployment. Below is the Helm side, a values snippet that prod would supply to override the chart defaults. Notice the values file is not Kubernetes YAML; it's the data that fills the chart's {{ .Values.* }} placeholders.
values-prod.yaml (Helm)
# Overrides the chart defaults for production. # The chart's templates read these via {{ .Values.* }}. replicaCount: 3 image: repository: registry.example.com/web tag: "1.8.0" resources: requests: cpu: 250m memory: 256Mi limits: cpu: "1" memory: 512Mi ingress: enabled: true host: app.example.com tls: true # Install with: # helm upgrade --install web ./web-chart -f values-prod.yaml Now the Kustomize side. The base is real, applyable YAML; the prod overlay is a tiny kustomization.yaml plus a patch that changes only what differs. No placeholders anywhere, every file would kubectl apply on its own.
overlays/prod/kustomization.yaml (Kustomize)
# overlays/prod/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: prod resources: - ../../base # the shared, real manifests images: - name: registry.example.com/web newTag: "1.8.0" # bump the tag without editing the base replicas: - name: web count: 3 patches: - patch: |- - op: replace path: /spec/template/spec/containers/0/resources/limits/memory value: 512Mi target: kind: Deployment name: web # Render with: # kubectl kustomize overlays/prod (or: kustomize build overlays/prod) 💡 Try it in the browser: Practice both flows hands-on in the Helm lab and the kubectl lab, render, diff, and apply without touching a real cluster.
How they fit GitOps
Neither tool is a deployment system, they both just produce YAML. That makes them a perfect fit for GitOps, where Git is the source of truth and a controller continuously reconciles the cluster to match. You commit the chart-plus-values or the base-plus-overlays, and the controller renders and applies them for you.
Both ArgoCD and Flux speak Helm and Kustomize natively. Point Argo at a chart and a values file, or at an overlay folder, and it runs helm template / kustomize build under the hood, diffs the result against live state, and self-heals drift. The win: humans never run helm install or kubectl apply against prod, they open a pull request, and the merge is the deploy.
- Render in the pipeline, not the cluster, let the GitOps controller own
helm template/kustomize buildso what's in Git is exactly what runs. - One repo, many overlays, keep
base/plusoverlays/dev|stage|prod(or one chart with per-env values) so a diff between environments is a real, reviewable diff. - Pin versions in Git, image tags and chart versions live in committed files, so every change has an author, a timestamp, and a one-click revert.
Common mistakes that cost hours
- Over-templating Helm charts. Exposing forty knobs in
values.yamlfor an app three teams use makes the chart unreadable and every upgrade scary. Template only what genuinely varies; hardcode the rest. - Putting logic in templates. Nested
{{ if }}/{{ range }}blocks turn a manifest into a program nobody can debug. When you find yourself writing conditionals around conditionals, the answer is usually a second values file, or Kustomize. - Letting environments drift. Hand-editing live resources with
kubectl edit, or applying an overlay manually "just this once," silently desyncs the cluster from Git. Render from source every time and let GitOps catch drift. - Forgetting Kustomize has no rollback. It emits YAML and walks away. If you need versioned releases and
helm rollback, that lifecycle is on you (or on your GitOps controller via Git history), don't assume the tool provides it. - Mixing both without a reason. Helm-inside-Kustomize and Kustomize-post-rendering-Helm are valid but advanced. Pick one as your default and only reach for the combo when a concrete need forces it.
Takeaways
The whole article in six lines
- Maintaining one set of manifests across environments by copy-paste guarantees drift, pick a packaging tool.
- Helm = templates: charts with {{ }} placeholders filled by per-env values, plus versioned, rollback-able releases.
- Kustomize = patches: a real-YAML base with small per-env overlays, no templating, built into kubectl.
- Choose Helm to distribute reusable apps with many knobs and rollback; choose Kustomize for your own apps with small, readable diffs.
- Both just render YAML, which makes them a natural fit for GitOps with ArgoCD or Flux.
- Avoid over-templating, logic in templates, and silent drift, render from source every time.
Where to go next
Packaging is one piece of running Kubernetes well. Pair it with the operational and delivery practices around it:
- Read Kubernetes in Production: Beyond the Tutorial for the reliability concerns your manifests need to encode.
- Read GitOps: Declarative Delivery with ArgoCD & Flux to see how rendered manifests get continuously reconciled.
- Practice in the Helm lab and the kubectl lab, author a chart, build an overlay, diff the output.
- Follow the full DevOps Engineer path to put packaging, pipelines, and observability together.
Originally published on TheSimplifiedTech, where this guide is interactive, with in-browser terminal labs and diagrams. Learn cloud and DevOps by doing, no videos.