skip to content

Alternatives & Boundaries

Where parameterised templating stops being the right answer, argued against each serious rival: overlay patching, a controller that keeps reconciling, and the honest case for no chart at all. Asked as a design argument you must be able to make in both directions.

part ofHelmoverview, primer and where to startread it →
on this pageshow

explore

questions

12

How do Helm and Kustomize differ in how they produce the final manifests applied to a cluster?

level: juniorimportance: must knowfreq 78%

answer

  1. Two routes to the same applied YAML
  2. One generates the YAML, one patches it
  3. Only the knobs the author exposed
  4. One of the two remembers what it applied
  5. sh.helm.release.v1 Secret per revision

basics

~20 s

Helm renders a chart's Go templates against values, so the final YAML is generated from files that are not YAML. Kustomize patches complete YAML instead. Helm also stores each apply as a release; kubectl apply -k stores nothing.

solid answer

~50 s

Helm's unit is a chart: `Chart.yaml`, `values.yaml` and a `templates/` directory of Go templates. `helm install` and `helm upgrade` render those templates against the merged values and apply the result, so an object can be emitted conditionally with `if` or generated in a loop with `range` — but only the knobs the chart author exposed as values are reachable without forking. Kustomize goes the other way: its inputs stay valid Kubernetes YAML and a variant is produced by patching, so any field in the base is reachable, while conditionally emitting a whole object is not what patching is for. The second difference matters just as much: Helm records every apply as a release revision in a Secret in the namespace, which is what makes `helm list`, `helm get manifest`, `helm rollback` and `helm uninstall` possible. `kubectl apply -k` leaves no such record.

code

yaml · 29 lines
yaml
# templates/statefulset.yaml — not valid YAML until rendered
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Release.Name }}-redis
spec:
  replicas: {{ .Values.replicaCount }}
  serviceName: {{ .Release.Name }}-redis
  selector:
    matchLabels:
      app: {{ .Release.Name }}-redis
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-redis
    spec:
      containers:
        - name: redis
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
{{- if .Values.persistence.enabled }}
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: {{ .Values.persistence.size }}
{{- end }}

go deeper

for a junior

Be ready to say in two sentences that Helm renders templates against values while Kustomize patches finished YAML, and that Helm additionally remembers each install as a release. Name Chart.yaml, values.yaml and templates/ without hesitating.

for a middle

Explain the mechanics: values merge into .Values, if and range decide what is emitted, and the render is stored as a release revision that later upgrades diff against. Say plainly which customisations each model makes easy and which it makes awkward.

for a senior

Show you have felt both costs in production: a chart knob that did not exist when you needed it, and an overlay estate with no answer to "what is actually deployed here?". Mention the post-renderer bridge rather than presenting the two as exclusive.

for a principal

Own the framing that this is a distribution and lifecycle decision, not a syntax preference. Charts are a versioned public interface for consumers you cannot redeploy; overlays are cheap when every consumer is you and you are willing to own deployment state elsewhere.

### Two different machines for producing YAML Both tools end up sending Kubernetes manifests to an API server, but they build those manifests in opposite ways, and the interview question is almost always really about that difference plus its consequences. **Helm generates.** A chart is a directory containing `Chart.yaml` (name, `version`, `appVersion`, dependencies), `values.yaml` (the defaults), and `templates/` — files written in Go's text/template dialect. `helm install` and `helm upgrade` merge the chart defaults with whatever the caller supplied, expose the result to the templates as `.Values` alongside the built-in objects `.Release`, `.Chart`, `.Capabilities`, `.Files` and `.Template`, render every file under `templates/`, and apply what comes out. The consequence people miss is that the files under `templates/` are not Kubernetes YAML at all before rendering — they are text with actions in them: ```yaml {{- if .Values.persistence.enabled }} volumeClaimTemplates: - metadata: name: data spec: resources: requests: storage: {{ .Values.persistence.size }} {{- end }} ``` A Redis chart written this way can emit a StatefulSet with a PersistentVolumeClaim for one caller and a plain Deployment with an `emptyDir` for another, from the same source, because whole blocks and whole objects appear and disappear. That expressive power has a price: the customisation surface is fixed in advance by the chart author. If the author never templated `topologySpreadConstraints`, a consumer cannot set it — there is no value to set. The options are then to fork the chart, upstream a new value, or post-process the rendered output. **Kustomize patches.** Its inputs are complete, valid manifests, and a variant is produced by applying patches to them. Nothing in the tree needs a rendering step before it can be read, linted or validated as a manifest, and no field is out of reach because nobody anticipated it — you can patch something the base's author never thought about. The symmetric cost is that "emit this object only in production" or "repeat this block once per queue" is not what patching expresses naturally. ### The second difference: state The rendering model is only half the answer, and candidates who stop there sound shallow. Helm keeps a record. Every `install`, `upgrade` and `rollback` writes a **release revision** — the rendered manifest, the values that produced it, the chart metadata and a status — into a Secret named `sh.helm.release.v1.<name>.v<rev>` in the release's namespace. That record is the basis of everything Helm can do that a plain apply cannot: - `helm list` enumerates what is installed in a namespace, without you knowing what to look for. - `helm get manifest <release>` prints exactly what that release applied — the render, after the fact. - an upgrade diffs the new render against the **stored previous manifest**, so a resource you deleted from the chart is deleted from the cluster. - `helm rollback` can re-apply an earlier revision because that revision's manifest is still on disk in the cluster. - `helm uninstall` knows precisely which objects belong to the release. `kubectl apply -k` has none of that: the built manifests are applied and forgotten. The cluster plus your source tree are the only state, so "what is deployed here?" and "put it back" are questions you answer with Git and a rebuild rather than with a command. ### The third difference: packaging Helm charts are also a **distribution format**. `helm package` produces a versioned `.tgz` that can be served from a classic chart repository with an `index.yaml`, or pushed to and installed from an OCI registry with an `oci://` reference. A chart therefore has an explicit public interface — `values.yaml`, optionally constrained by `values.schema.json` — that strangers depend on. That is why nearly everything you install from a vendor arrives as a chart, and why most organisations end up running Helm at least as a consumer regardless of what they choose for their own services. ### How to say it in an interview A crisp answer covers three axes: **generation versus patching** (templating power and a fixed knob surface, against unconstrained patching of real YAML); **stateful release versus stateless apply** (revision history, rollback, uninstall, deletion of removed resources, against nothing but the cluster); and **packaging** (a versioned, parameterised artifact for consumers you cannot redeploy yourself). Then say the honest thing: they are not mutually exclusive. Helm can hand its rendered output to a post-renderer, and plenty of teams consume third-party charts while writing their own services as plain manifests with overlays. One myth worth killing on the spot: Helm has no server-side component. Since Helm 3 there is no Tiller — the CLI renders locally and talks to the API server with your own credentials, exactly as `kubectl` does.

  • A chart does not expose the field you need as a value. What are your options short of forking it?
    Three. Upstream a new value into the chart if you can, which is the durable fix. Patch the rendered output with a post-renderer, so Helm still owns the release record. Or, for a small number of fields, layer a separate manifest of your own beside the release. Forking is the last resort because you then own every future upgrade of that chart.
  • Does Helm need to reach the cluster to render a chart's templates?
    Rendering itself is local Go templating, but `install` and `upgrade` populate `.Capabilities` from the API server's discovery data, so a chart branching on whether an API version exists needs a real connection to branch correctly. `helm template` renders offline against built-in defaults instead, which is why a chart can render differently there than it does on install.
  • Since Helm 3 there is no Tiller. What does that change about who is allowed to install a chart?
    The CLI talks to the API server as the user running it, so Helm can do exactly what your RBAC permits and nothing more. There is no cluster-wide privileged component that installs on your behalf, so chart installation is governed by ordinary Kubernetes authorization rather than by a separate grant.

Helm is a parameterised form letter: powerful, but you can only fill in the blanks the author left. Kustomize is editing a finished letter with a red pen: any word is fair game, but you cannot make whole paragraphs appear from a flag.

saying these in an interview costs you the question

  • Says Helm and Kustomize are the same idea with different syntax
  • Describes Kustomize as a template language with variables
  • Claims files under templates/ are valid YAML before rendering
  • Says Helm installs need a server-side Tiller component
  • Thinks kubectl apply -k leaves a release you can roll back
  • Assumes any chart field can be overridden without a value for it

context

open as a page

Why is helm upgrade described as a one-shot apply rather than a reconcile loop?

level: juniorimportance: must knowfreq 72%

basics

~20 s

helm upgrade runs once: it renders the chart, sends the result to the API server, records a new release revision and exits. Nothing then watches the cluster, so drift survives until somebody runs Helm again.

open as a page

Why can you run helm rollback on a release but not undo a kubectl apply -k the same way?

level: middleimportance: must knowfreq 62%

basics

~20 s

Each helm install or upgrade stores the rendered manifest, its values and a status as a release revision in a namespace Secret, so helm rollback can re-apply an earlier one. kubectl apply -k stores nothing; undoing it means rebuilding the older source.

open as a page

What does dropping a Helm release for plain kubectl apply actually cost you?

level: middleimportance: must knowfreq 61%

basics

~20 s

You lose the release record: the stored manifest and values per revision, helm history and helm rollback, membership tracking that prunes removed resources and cleans up on uninstall, hook ordering, helm test, and the wait behaviour Helm applies after writing.

open as a page

When do plain Kubernetes manifests applied with kubectl beat writing a Helm chart?

level: juniorimportance: should knowfreq 52%

basics

~20 s

When there is one deployment target and nothing genuinely varies between deployments. A chart then adds Chart.yaml, templates/ and a values indirection to produce the YAML you already wrote, and reviewers must render it to see what ships.

open as a page

helm upgrade keeps fighting an operator over a field both set — how do you fix it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Stop declaring the field in the chart — whichever side is not its owner must leave it out. Otherwise Helm 4's server-side apply fails the upgrade with a field-manager conflict, and Helm 3's client-side merge silently overwrites the controller on every run.

open as a page

A Helm chart's templates nest if and range so deeply nobody can predict the output. Now what?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Measure before deciding: render every values combination you actually ship with helm template and count the distinct outputs. Few distinct shapes means separate charts; many means the branching is program logic that templating is the wrong medium for.

open as a page

Would you standardise 43 internal services on Helm charts or Kustomize overlays, and how would you decide?

level: principalimportance: should knowfreq 41%

basics

~20 s

Decide on who consumes the output and what lifecycle you need. Charts earn their cost when a versioned, parameterised artifact ships to consumers you cannot redeploy, or when rollback, ordered hooks and uninstall matter. Otherwise overlays keep first-party YAML readable.

open as a page

How do you split responsibility between Helm releases and an operator's custom resources for a stateful platform?

level: principalimportance: should knowfreq 38%

basics

~20 s

Helm owns what is decided at deploy time — packaging, version pinning, per-tenant configuration, release history. The operator owns everything triggered by cluster events — backup, failover, staged version migration. The test is what has to happen when nobody is at a keyboard.

open as a page

What goes into a Helm chart whose job is installing an operator, and what stays out?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

It ships the controller's own runtime — its CustomResourceDefinitions, Deployment, ServiceAccount and RBAC — with values describing the controller: image, watched namespaces, resources. The custom resources describing real instances normally live in a separate release.

open as a page

What do you lose by rendering a chart with helm template and patching the output before kubectl applies it?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

You lose everything the release record provides: no history, rollback, uninstall or deletion of removed resources. Hooks become ordinary manifests nothing sequences, crds/ content is omitted unless asked for, and .Capabilities reflects the client rather than the cluster.

open as a page

How would you choose between a Helm chart and a typed generator such as cdk8s or jsonnet?

level: principalimportance: nice to knowfreq 33%

basics

~20 s

Split the decision in two: who produces the YAML, and who tracks what was applied. A generator replaces only the first. If the artefact leaves your team, a chart is the interchange format; if it never does and the logic is real programming, a generator usually wins.

open as a page