Back to Blog
DevOps12 min readJun 2026

Helm & Kustomize: Packaging Kubernetes Manifests

Two ways teams tame Kubernetes YAML across environments: Helm's templated charts and Kustomize's overlays. When to reach for each, and how both fit into GitOps.

DevOpsKubernetesHelmKustomize
SB

Sri Balaji

Founder · TheSimplifiedTech

On this page

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 -f` and a Deployment are familiar, you're ready. New to running clusters? Start with [Kubernetes in Production: Beyond the Tutorial](/blog/kubernetes-in-production) 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.

A mail-merge letter with {{name}} blanks you fill from a spreadsheetA Helm chart with {{ .Values.x }} placeholders filled from values.yaml
A printed master letter with sticky-note corrections clipped on topA Kustomize base manifest with overlay patches applied per environment
The spreadsheet of names and addressesEach environment's values file (dev / staging / prod)
A bundle of letters mailed under one tracking numberA 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:

templatedpatchedhelm templatekustomize buildapply
Base manifests

Deployment, Service, Ingress

Helm

chart + values.yaml

Kustomize

overlays / patches

Per-env output

rendered YAML

Cluster

dev / stage / prod ns

Base manifests flow through Helm values OR Kustomize overlays into per-env output, then to the cluster.

  1. 1

    Author the base once

    Write the Deployment, Service, and Ingress with sensible defaults. This is the single source of truth that every environment shares.

  2. 2

    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.

  3. 3

    Render the final YAML

    `helm template` or `kustomize build` turns base + env-input into concrete, fully-resolved manifests. No placeholders left.

  4. 4

    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.

DimensionHelmKustomize
TemplatingYes, Go templates with {{ }}, conditionals, loops, functionsNo, plain YAML only, diffs via strategic-merge & JSON patches
ReleasesFirst-class: named, versioned installs you can roll back with one commandNone, it just emits YAML; lifecycle is whatever applies it
Packaging & sharingCharts are versioned, published to registries, installed by anyoneNo package format; you copy/fork or reference a remote base by URL
Learning curveSteeper, a template language, values precedence, chart structureGentle, if you know YAML and diffs, you mostly already know it
ToolingStandalone binary; rich ecosystem (Artifact Hub, hooks, tests)Built into kubectl (`kubectl apply -k`) plus the standalone CLI
When to useDistributing reusable apps; lots of env knobs; need rollbackYour 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)
yaml
# 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)
yaml
# 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](/labs/helm) and the [kubectl lab](/labs/kubectl), 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 build so what's in Git is exactly what runs.
  • One repo, many overlays, keep base/ plus overlays/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

  1. Over-templating Helm charts. Exposing forty knobs in values.yaml for an app three teams use makes the chart unreadable and every upgrade scary. Template only what genuinely varies; hardcode the rest.
  2. 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.
  3. 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.
  4. 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.
  5. 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:

Want to go deeper?

This article covers concepts taught hands-on in the Cloud Engineer and DevOps career paths, with real terminal labs, production scenarios, and structured lessons.