skip to content

A Helm values string containing `{{ .Release.Name }}` renders literally into the manifest — why, and what fixes it?

level: middleimportance: should knowfreq 41%

answer

  1. Values are data, not program text
  2. Nothing re-reads the string by default
  3. A Helm function takes the string then a context
  4. The chart author has to opt the field in

basics

~20 s

Values are data, not templates: Helm renders the files under templates/, and a string arriving from a values file is inserted verbatim. To evaluate it, the chart must pass it through Helm's tpl function with a context: {{ tpl .Values.ingress.host . }}.

solid answer

~40 s

Helm's render pass compiles the chart's own template files. Values are just the data those files read, so braces inside a values string are ordinary characters and reach the manifest unchanged. Helm provides `tpl` for the case where a chart deliberately wants a value to be template source: `{{ tpl .Values.ingress.host . | quote }}` parses that string as a template and executes it at render time. The second argument is the context the string is evaluated against, and it decides what `.Values` and `.Release` mean inside it — inside a subchart, passing `.` gives the subchart's scope, not the parent's. The chart author has to opt in, so this only works where the chart already wrapped that field in `tpl`; a chart consumer cannot make an arbitrary value template-aware from the outside.

code

yaml · 8 lines
yaml
# values.yaml — a string that only looks like a template
ingress:
  host: "{{ .Release.Name }}-scoring.internal.example"

# templates/ingress.yaml — the chart must opt the field in
spec:
  rules:
    - host: {{ tpl .Values.ingress.host . | quote }}

go deeper

for a junior

Know the rule: what you write in a values file is data, so braces come through literally, and only a chart that wraps the field in tpl will evaluate them.

for a middle

Explain the render pass — chart files are the program, values are the input — and describe tpl's signature: the string first, then the context that decides what .Values and .Release mean inside it.

for a senior

Show judgment about the surface: tpl executes caller-supplied text at render time with the installer's privileges, the error points at the calling template rather than the values file, and re-parsing inside a loop is a real cost.

for a principal

Decide how much of a shared chart's contract may be templated at all, document which fields are and against which context, and set the review expectation for values files that feed those fields.

## Why the braces survive Rendering a chart is a single, well-defined pass: Helm parses each file under `templates/`, executes it against a context built from the merged values, the release metadata, `Chart.yaml` and the cluster capabilities, and collects the text. Values are one of the inputs to that execution, not part of the program being executed. So a values file that says ```yaml ingress: host: "{{ .Release.Name }}-scoring.internal.example" ``` hands the template a plain string that happens to contain braces. `{{ .Values.ingress.host }}` writes those characters into the manifest, the API server stores them, and a single-service chart with an Ingress and an autoscaler comes up serving traffic on a hostname nobody can resolve. There is no second pass that would notice. ## `tpl` is the opt-in second pass Helm adds a `tpl` function for exactly this: it takes a string and a context, parses the string as a template, executes it, and returns the result. The chart writes ``` host: {{ tpl .Values.ingress.host . | quote }} ``` and from then on a caller may put template actions in that one field. Note the argument order — the string first, the context second — and note who has to make the change: the **chart author**. A consumer cannot make a value template-aware from the outside; if the chart interpolates the field plainly, the only options are to ask for `tpl` on that field or to compute the string at the call site. ## The context argument is the interesting half The second argument decides what the string's own actions can see. Passing `.` gives the string the same context the surrounding template has, which is what you almost always want at the top level of a chart. Inside a subchart the same `.` is the subchart's context, so `.Values` there means the subchart's values, not the umbrella's — a string written by someone thinking about the parent will resolve to nothing. Passing a dict instead lets a chart offer a deliberately narrow surface: the string can then see only the keys you put in it. A related use is rendering a whole configuration file that a chart ships as an asset rather than as a template, by reading the file's contents and passing them through `tpl`. Same mechanism: the text becomes template source, evaluated against the context you nominate. ## What it costs and what it risks `tpl` parses on every call. One field per manifest is free; a `tpl` inside a range over the members of a large list re-parses for each element, and on a big umbrella that shows up as a render that takes seconds instead of milliseconds. Hoist the parse out of the loop when the string does not depend on the loop variable. The risk is subtler and worth saying out loud in an interview: the string is executed with the same function set the chart's own templates have. A values file is usually reviewed less carefully than chart source, and it may be supplied by another team, generated by a pipeline, or committed to a config repository with looser review. Whatever ends up in a `tpl`-rendered field runs at render time with the privileges of whoever runs the install, including anything that reaches the cluster. Treat a `tpl` field as an execution surface: use it for the small, specific values that genuinely need release-awareness — hostnames, annotation values, a job argument — and not as a general escape hatch for arbitrary chart logic supplied from outside. ## Reading the failure Two failure shapes are common. The first is the literal braces described above, which is not an error at all — it renders, applies, and only fails as a wrong hostname or an annotation nothing matches. Render the chart and read the field; the braces are right there in the output. The second is a render error from a malformed string: an unclosed action, or a field that does not exist in the context you nominated. The message is attributed to the template that called `tpl`, because that is what the engine was executing — it does not point at the line in the values file where the bad string actually lives. When a chart uses `tpl` in several places, the fastest way to localise it is to render with only one of the suspect values set, or to bisect the values file, rather than to stare at the reported template line. ## Where it belongs in a chart's design A field rendered through `tpl` is part of the chart's public contract, so document it: say in the values documentation that the field is templated and against which context, because to a consumer the difference between a plain string field and a templated one is invisible until it misbehaves.

  • Which context should a subchart pass as `tpl`'s second argument?
    Normally `.`, which inside a subchart is the subchart's own context — so `.Values` in the string means that subchart's values, and `.Release` still means the release. That is usually what a consumer of the subchart expects. If you want the string to see only a narrow surface, pass a dict you build instead. The one thing to avoid is documenting the field as templated without saying which context it is evaluated against.
  • A malformed templated value breaks the render — where does the error point?
    At the template that called `tpl`, not at the values file, because that is the template the engine was executing when the parse of the injected string failed. With several templated fields it is faster to bisect the values — render with one suspect field set at a time — than to read the reported line. That indirection is a real argument for keeping the number of templated fields small and documented.
  • Should a chart make many of its values templated for flexibility?
    No. Each `tpl` field is an execution surface: the string runs at render time with the chart's full function set and the installer's privileges, and values files are typically reviewed less carefully than chart source. Reserve it for fields that genuinely need release-awareness — a hostname, an annotation value, a job argument — and expose real structured values for everything else. Repeated parsing also costs render time when it sits inside a loop.

saying these in an interview costs you the question

  • Thinks values files are rendered as templates by default
  • Believes a consumer can force templating without chart support
  • Calls tpl with no context argument
  • Assumes tpl only applies to files, not values
  • Treats a values string from another team as inert data
  • Puts tpl inside a large loop without considering the parse cost

context