skip to content

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

level: seniorimportance: should knowfreq 44%

answer

  1. Turn the complaint into a count
  2. Render every values file you really ship
  3. Distinct outputs, not values combinations
  4. Repetition and conditionality want different fixes
  5. Abandoning templating is the expensive option

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.

solid answer

~50 s

Start with evidence rather than taste. Run `helm template` once per values file you genuinely deploy, diff the results, and count how many distinct manifests the chart really produces. A library chart consumed by twelve service charts that emits three shapes has three charts hiding inside it, and splitting removes the conditionals outright. If the outputs are genuinely all different, ask what the branching is doing: value defaulting and shape selection belong in templates, but arithmetic, cross-object invariants and data reshaping do not, because Go templates over YAML are untyped, have no tests worth the name, and can emit structurally broken documents that only rendering reveals. At that point the choices are extracting named templates into a library chart, splitting the chart, or moving generation to a typed generator such as cdk8s or jsonnet and keeping Helm only where you still want a release.

code

bash · 7 lines
bash
for v in values/service-*.yaml; do
  helm template gateway ./chart -f "$v" > "render/$(basename "$v" .yaml).yaml"
done
sha256sum render/*.yaml | awk '{print $1}' | sort -u | wc -l

helm template gateway ./chart -f values/service-a.yaml \
  --show-only templates/deployment.yaml --debug

go deeper

for a junior

Know that a chart's templates are rendered before anything is applied, and that helm template shows you that output. Being able to reach for the render rather than guessing from the template text is the skill here.

for a middle

Explain why nested conditionals are risky in this medium specifically: Go templates are untyped text substitution over YAML, so a misplaced branch can emit a valid-but-wrong or structurally broken document that only rendering reveals.

for a senior

Demonstrate measurement before redesign — count distinct renders across the values files you really ship — and then match the fix to the cause, separating repetition (a library chart) from conditionality (splitting, or leaving templating).

for a principal

Set the standard for when a shared chart may branch at all, and own the migration cost when a widely consumed chart is split: every consumer's render changes, and the organisation needs a way to see that before it ships.

### Diagnose the sprawl before you redesign it "Nobody can read it" is a feeling; turn it into a number. For each values file you actually ship, render the chart and keep the output: ```bash for v in values/*.yaml; do helm template gateway ./chart -f "$v" > "render/$(basename "$v" .yaml).yaml" done sha256sum render/*.yaml | sort ``` Two numbers fall out. The first is how many *distinct* manifests the chart produces across real usage. The second is how many values keys exist only to feed a conditional rather than to set a field. In a library chart consumed by twelve service charts, it is common to find twelve values files collapsing to three or four distinct shapes: the conditionals are encoding a small number of archetypes, and the chart is paying combinatorial complexity to express a categorical choice. ### Read the render, not the template Helm gives you the tools to work at the output level, and they are the ones to reach for during triage: `helm template --debug` shows the parsed values alongside the render, `--show-only templates/deployment.yaml` narrows the output to a single file, `helm lint` catches structural problems in the chart, `--dry-run=server` sends the render to the API server for validation without persisting it, and `helm get manifest` shows what a live release actually applied. If your team can only reason about the chart by rendering it, that is data: the source of truth for review has already moved to the output, and you are maintaining a generator that nobody reads. ### The signals that templating has outgrown its job - Conditionals nested three or more deep, particularly `if` inside `range` inside `with`, where the meaning of dot at any given line is genuinely hard to state. - Values keys whose only purpose is to switch other values on, forming an untyped, undocumented mini-language. - Whitespace and indentation bugs that are found by rendering rather than by reading. - A change that requires touching `_helpers.tpl` and re-rendering all consumers to be sure nothing moved — the blast radius of an 18-chart umbrella is the whole umbrella. - Logic that is arithmetic or invariant-checking rather than shape selection. ### The four ways out, in increasing cost **Split the chart.** If the render collapses to three shapes, ship three charts. This is the cheapest fix and the most often skipped, because splitting feels like duplication — but duplication between three readable charts beats one chart that nobody can evaluate. **Extract into a library chart.** A chart with `type: library` in `Chart.yaml` installs nothing; it exists to publish named templates that other charts `include`. This is the right home for shared boilerplate such as label blocks and naming helpers. It concentrates the shared parts rather than reducing branching, so it helps when the problem is repetition and not conditionality. **Flatten by pushing the choice up.** Replace `enabled` flags with per-consumer values files that state the whole shape, so the chart branches less and the caller states more. Verbose, but readable, and it makes the render diff meaningful in review. **Stop templating.** If the logic is real program logic, generate the manifests with a typed generator — cdk8s expresses them as objects in a general-purpose language, jsonnet as a data-templating language with functions and composition — and let ordinary language tooling handle abstraction, testing and refactoring. Crucially this replaces only the rendering layer: the generated YAML still has to reach a cluster, and if you still want release history you either keep the output as a simple chart's static templates or accept plain applies. ### Do not confuse this with the packaging decision An unreadable chart is an argument against *this chart's design*, not automatically against Helm. Many charts get rescued by splitting alone. The stronger claim — that templating is the wrong medium — should rest on what the branching does, not on how it currently looks, because a badly written chart and an inherently unsuitable one look identical from the outside. ### What an interviewer is listening for Evidence first, then options with costs. A senior answer counts distinct outputs, names the specific Helm commands used to see them, distinguishes repetition from conditionality, and treats abandoning templating as the expensive last option rather than the opening move. An answer that jumps straight to "rewrite it in a real language" has skipped the measurement that would tell you whether the rewrite is needed.

  • The team argues that splitting one chart into three duplicates the shared labels and naming helpers. How do you answer that?
    Shared boilerplate is a repetition problem, and the fix for repetition is a library chart: a chart with `type: library` that installs nothing and publishes named templates the three charts `include`. That removes the duplication without reintroducing the conditionals, because each chart still states its own shape. Conflating repetition with conditionality is what produced the unreadable chart in the first place.
  • How would you stop a chart from drifting back into deep nesting after you have refactored it?
    Make the render the reviewed artefact. Commit golden renders for each values file you ship and have CI re-render and diff them on every change, so any pull request shows what actually moves in the output. It turns invisible template edits into visible manifest diffs, and a change that touches every golden file is immediately visible as the blast radius it is.
  • Which kinds of logic are legitimately a chart's job, even when they need conditionals?
    Defaulting a value when the caller did not set one, including or omitting an optional object, selecting between API shapes based on what the cluster supports, and iterating a list the caller provided. Those are shape selection driven by input. Arithmetic, cross-object invariants, and reshaping data structures are where templating stops being the right medium, because none of them are checkable without rendering.

saying these in an interview costs you the question

  • Judging a chart unreadable without rendering its real outputs
  • Jumping straight to a rewrite in a general-purpose language
  • Treating a library chart as a fix for too many conditionals
  • Assuming helm lint catches wrong-but-valid rendered output
  • Believing splitting a chart always means duplicating logic

context