skip to content

Why do Helm charts write `toYaml .Values.resources | nindent 12` rather than `indent 12`?

level: middleimportance: must knowfreq 70%

answer

  1. The n stands for something
  2. Only the first line differs
  3. The template line already has spaces
  4. Pair the dash with the newline form
  5. Count the parent's column, add two

basics

~20 s

Both prefix every line of the string with N spaces; nindent also emits a leading newline. Because the action sits after literal indentation on the template line, indent double-indents just its first line, while nindent starts the block on a fresh line at exactly N spaces.

solid answer

~50 s

`indent 12` adds twelve spaces to the start of every line of its input, including the first. `nindent 12` does the same but writes a newline first, so the block begins on a line of its own. That difference matters because the action almost always sits after literal spaces in the template — it is written underneath `resources:` at the template's own indentation. With `indent`, the first emitted line inherits those literal spaces *and* the twelve, landing deeper than the lines beneath it, and the mismatch breaks the document. The robust idiom is `{{- toYaml .Values.resources | nindent 12 }}`: the leading dash deletes the template's literal indentation and the newline before it, and `nindent` supplies the newline plus exactly twelve spaces on every line. `indent` is the right choice only when the action starts at column zero and no newline is wanted.

code

yaml · 5 lines
yaml
containers:
        - name: tile-server
          image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

go deeper

for a junior

Learn the idiom as a unit: leading dash, toYaml, pipe, nindent, a number that matches the parent's depth plus two. Being able to write it correctly from memory is the bar here.

for a middle

Be ready to explain, byte by byte, why the first emitted line is the one that breaks: the template's literal spaces are real output, and only the newline form starts the block cleanly.

for a senior

Demonstrate the review instinct — checking the splice against the rendered document rather than the template — and explain why a single-key value can hide the defect until someone's values file grows a second key.

for a principal

Frame it as a contract question: how much structure a chart should splice wholesale from values at all, versus rendering named fields, and what that choice costs consumers who must guess the shape.

## Three pieces, one idiom The expression `{{- toYaml .Values.resources | nindent 12 }}` is doing three separate jobs, and the interview question is really about whether you can name them. `toYaml` takes a value out of `.Values` — a map, a list, a scalar — and serialises it as YAML text. The output starts at column zero, carries no leading indentation of its own, and has no trailing newline. That last property is deliberate and useful: it means the spliced block does not push a stray blank line into the manifest. `indent` and `nindent` then place that text. `indent 12` prefixes twelve spaces to every line of the string, first line included. `nindent 12` prefixes a newline to the whole thing and then indents every line by twelve. The `n` is for the newline; the indentation behaviour is identical. The leading `{{-` deletes the whitespace sitting in front of the action in the template file, including the newline that ended the previous line. ## Why the first line is the whole problem A splice is written where the value belongs, so the action is indented in the template to match its surroundings: ```yaml resources: {{ toYaml .Values.resources | indent 12 }} ``` Those twelve literal spaces before `{{` are real text and are emitted. Then `indent 12` adds twelve more to the first line of the serialised value. The first key of the block therefore lands at column twenty-four while every following key lands at column twelve. A mapping whose first key is deeper than its siblings is not a valid block mapping, and the render fails when Helm parses it. Swapping in `nindent` and adding the leading trim marker fixes both halves at once: ```yaml resources: {{- toYaml .Values.resources | nindent 12 }} ``` The dash removes the newline and the twelve literal spaces, `nindent` puts the newline back, and every line of the value — first one included — gets exactly twelve spaces. What the template looks like and what the output looks like are now decoupled, which is why the pairing has become the house style of essentially every published chart. ## Picking the number The number is the column the block's keys must occupy in the rendered document, not a relative offset. Count the parent key's indentation in the output and add two: a container's `resources:` sitting at ten under `containers:` needs its keys at twelve. It is a rendered-document measurement, so when a chart body is assembled from partials, the reliable way to check it is to read the render rather than the template. ## When indent is still correct `indent` is not a legacy spelling. It is right whenever the action begins at column zero of its template line, because then there is no literal indentation to double up and no newline to reinsert. It is also right when you are indenting a string you have already assembled with its own leading newline. The rule of thumb: if there is whitespace between the start of the line and the `{{`, you want the trim marker and `nindent`. ## The edge cases that surprise people An empty map serialises to `{}` and an empty list to `[]`, each on a single line. `nindent 12` will happily place that on its own line, so `resources:` ends up holding an empty map rather than being absent. That is usually harmless, but authors who want the key gone entirely guard the whole block instead of relying on the indentation functions to make it disappear. A single-key value hides mistakes. If the block has exactly one line, the `indent`-after-literal-spaces bug produces a line that is merely deeper than it needs to be, and extra depth under a key means nothing in YAML — so it parses and works. The same chart breaks the moment someone supplies two keys. A chart that has been fine for months can fail on a values change for exactly this reason. Finally, the pipeline order is not cosmetic. `toYaml .Values.resources | nindent 12` serialises first and indents the resulting text. Reversing it makes no sense: `nindent` works on a string, so it must be downstream of whatever produced the string. ## What good looks like In review, three things are worth checking on any splice: that the action carries a leading trim marker, that it uses `nindent` rather than `indent`, and that the number matches the parent's column in the rendered output rather than in the template. Those three checks catch nearly every indentation defect a chart can have before it reaches a cluster.

  • When is `indent` the right function to reach for in a Helm template?
    When the action starts at column zero of its template line, so there is no literal indentation to double up and no newline to reinsert. It is also right for a string you have already built with its own leading newline. The test is simply whether whitespace sits between the start of the line and the `{{`.
  • What does `toYaml` produce for an empty map, and what does `nindent` then do with it?
    An empty map serialises to `{}` on one line, and `nindent` places that on its own line under the key. The key survives holding an empty map rather than disappearing, so a chart that wants the key omitted entirely has to guard the whole block instead of relying on the indentation functions.
  • How do you decide the number passed to `nindent` in an unfamiliar chart?
    Read it off the rendered document, not the template. Find the parent key's column in the output and add two. Templates assembled from partials can place a block at a depth the template file does not show, so the render is the only reliable measurement.

Think of the template line as a shelf that already has twelve inches of margin drawn on it. indent measures its margin from wherever the pen currently rests, so the first line gets both margins; nindent moves to a fresh line first, so every line is measured from the same edge.

saying these in an interview costs you the question

  • Says indent does not indent and only nindent does
  • Thinks nindent appends a trailing newline rather than a leading one
  • Believes toYaml already indents output to the caller's depth
  • Blames toYaml when indent after literal spaces breaks the render
  • Picks the depth number by trial and error rather than counting
  • Claims a leading {{- can replace nindent entirely

context