skip to content

Templating & Values

The render pipeline in the order a value travels it: supplied on the command line, merged against defaults, bound in a template, emitted as YAML, and debugged when the output is not what you meant. Most Helm bugs are rendering bugs, which is why this is probed hardest.

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

explore

questions

page 1 of 2

In a Helm chart, which built-in objects are bound before the templates render?

level: juniorimportance: must knowfreq 76%

answer

  1. Seven names exist before any function runs
  2. One holds user input, one holds the release
  3. Capitalized because they are Go struct fields
  4. Release is shared, Chart is per-chart
  5. Values, Release, Chart, Files, Template, Subcharts

basics

~10 s

Helm binds .Values, .Release, .Chart, .Files, .Capabilities, .Template and, in a parent chart, .Subcharts. .Values holds the merged user values, .Release describes this install, and .Chart mirrors Chart.yaml with capitalized field names.

solid answer

~40 s

Before the first template action runs, Helm builds one root value and hangs seven members off it. `.Values` is the merged result of the chart's `values.yaml` plus whatever the caller passed. `.Release` describes this particular install: `Name`, `Namespace`, `Revision`, `IsInstall`, `IsUpgrade` and `Service`, which is always the string `Helm`. `.Chart` is the parsed `Chart.yaml`, but as Go struct fields, so the key `appVersion` is reachable as `.Chart.AppVersion`. `.Files` reads non-template files packaged with the chart. `.Capabilities` reports what the target cluster supports. `.Template` names the file currently rendering. `.Subcharts` gives a parent read-only access to a dependency's chart and values. All of them are read-only during a render; a template cannot assign to them.

code

yaml · 11 lines
yaml
metadata:
  name: {{ .Release.Name }}-{{ .Chart.Name }}
  namespace: {{ .Release.Namespace }}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
    app.kubernetes.io/instance: {{ .Release.Name }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    app.kubernetes.io/managed-by: {{ .Release.Service }}
    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
  annotations:
    rendered-by: {{ .Template.Name }}

go deeper

for a junior

Be able to list the objects and say what each one holds, and get the spelling right: .Chart.AppVersion, not .Chart.appVersion. Knowing that .Release.Service is always the string Helm is a cheap win.

for a middle

Explain why the fields are capitalized while .Values keys are not, and the difference between .Chart.Version and .Chart.AppVersion. Be ready to say what a subchart's template sees for each object.

for a senior

Show that you know which objects are shared across a release and which are per-chart, and that everything derived from them is frozen into the stored manifest at render time rather than re-evaluated later.

for a principal

Own the convention: which built-ins your organisation's charts are allowed to bake into names and labels, and what that costs when a chart is renamed, re-parented or vendored as a dependency.

### What "built-in object" means A Helm chart's `templates/` directory is not YAML: it is Go text/template source that happens to produce YAML. Before Helm executes the first action in those files, it constructs a single root value and binds it to the dot. Everything a template can reach without calling a function hangs off that root, and the top-level members of it are what Helm calls the *built-in objects*. There are seven, and they are the entire vocabulary a chart starts with. They all begin with a capital letter because they are Go struct fields, and Go only exports capitalized names. That single fact explains the most common beginner error on this topic: the key in `Chart.yaml` is `appVersion`, but the template spells it `.Chart.AppVersion`. `.Values` is the exception you never have to capitalize *into* - the keys underneath it are whatever the values file literally wrote, so `.Values.image.tag` stays lowercase all the way down. ### The seven **`.Values`** is the merged values tree: the chart's own `values.yaml` defaults, overlaid by whatever the caller supplied. What ends up in it is the values-precedence question and belongs to its own topic; from inside a template it is simply a nested map, and a key nobody set is missing rather than empty, which is why `default` and `required` exist. **`.Release`** describes this install. `.Release.Name` is the release name chosen at install time and fixed for the release's life. `.Release.Namespace` is the namespace the operation targets. `.Release.Revision` is the revision being created - 1 on install, one higher than the current revision on upgrade. `.Release.IsInstall` and `.Release.IsUpgrade` say which operation is rendering. `.Release.Service` is always the literal string `Helm`, which is why the conventional label is written `app.kubernetes.io/managed-by: {{ .Release.Service }}` instead of hard-coding it. There is no timestamp member: Helm 3 removed the old `.Release.Time`, and reaching for sprig's `now` instead makes every render produce different output. **`.Chart`** is the parsed `Chart.yaml` - `Name`, `Version`, `AppVersion`, `Type`, `Description`, `Annotations` and the rest. Two of these are routinely confused: `Version` is the chart's own version and `AppVersion` is the version of the software the chart ships, which is why the standard label set uses `{{ .Chart.AppVersion | quote }}` for the app and `{{ .Chart.Name }}-{{ .Chart.Version }}` for the chart. **`.Files`** reaches the non-template files packaged inside the chart - `.Files.Get` for one file's contents, `.Files.Glob` for a set. Files under `templates/` are not visible through it, and neither is anything `.helmignore` excluded from the package. **`.Capabilities`** reports what the target cluster can accept. It exists here for completeness; how you interrogate it is a separate subject. **`.Template`** describes the file being rendered right now: `.Template.Name` is the path of the current template inside the chart, such as `payments-ledger/templates/deployment.yaml`, and `.Template.BasePath` is its directory. It is used almost exclusively for error messages and debug annotations. **`.Subcharts`** lets a parent chart read a dependency's chart object and values, addressed by the dependency's name or alias. It is read-only in that direction only; a subchart has no matching way back up. ### Which of them a subchart sees differently This is the part interviewers actually probe. When Helm renders a chart that has dependencies, it does not render one flat context. `.Release` is the same object everywhere - a single release name, namespace and revision covers the parent and all of its dependencies, which is exactly why every object in the release can be labelled consistently. `.Chart`, by contrast, is per-chart: inside a subchart's own template, `.Chart.Name` and `.Chart.Version` are the *subchart's*, not the parent's. `.Values` is scoped too - a subchart sees its own section of the tree at the top level. So in a 41-service platform chart, `{{ .Release.Name }}` is one shared string across every rendered file while `{{ .Chart.Name }}` differs in each subchart's output, and a name built from both is unique per service without any manual prefixing. ### Read-only, and evaluated once None of these objects can be assigned to. A template can copy a value into a variable, pipe it through functions, or pass a different value as the dot to a partial, but the bound objects themselves do not change during a render. That also means everything derived from them is decided at render time and then frozen into the manifest Helm stores with the release - the rendered text is what gets applied, and re-reading the release later shows the values as they were, not as they would render today.

  • Why is it .Chart.AppVersion when Chart.yaml spells the key appVersion?
    Because `.Chart` is a Go struct, not the raw YAML map, and Go exports only capitalized field names. Helm maps each `Chart.yaml` key onto its struct field, so `appVersion` becomes `AppVersion`, `apiVersion` becomes `APIVersion`. `.Values` behaves differently: it really is a map of whatever keys the values file wrote, so those stay exactly as typed.
  • Inside a subchart's own template, what does .Chart refer to, and what does .Release refer to?
    `.Chart` is the subchart's own `Chart.yaml` - its name and version, not the parent's. `.Release` is the same object the parent sees: one release name, namespace and revision covers the whole install. That asymmetry is why a name composed of `.Release.Name` plus `.Chart.Name` is unique per subchart without any extra prefixing.
  • What is .Template.Name useful for?
    It holds the path of the template file currently rendering, relative to the chart root, and `.Template.BasePath` holds its directory. In practice it is used to stamp a debug annotation onto an object so you can trace a rendered resource back to the file that produced it, and inside `fail` or `required` messages so the error names its own source file.

saying these in an interview costs you the question

  • Says .Release.Time gives the install timestamp
  • Writes .Chart.appVersion with the Chart.yaml spelling
  • Thinks .Values contains only values.yaml defaults
  • Believes a template can assign to a built-in object
  • Expects .Chart to be the parent chart inside a subchart
  • Confuses .Chart.Version with .Chart.AppVersion

context

open as a page

Why does Helm's `lookup` function return nothing when a chart is rendered with `helm template`?

level: juniorimportance: must knowfreq 58%

basics

~20 s

helm template never contacts an API server, so Helm's lookup has nothing to query and returns an empty map rather than failing. Only a render that reaches a cluster — a real install, an upgrade, or --dry-run=server — fills it in.

open as a page

How do you render a Helm chart to YAML locally, and what does helm template --show-only do?

level: juniorimportance: must knowfreq 78%

basics

~20 s

helm template <release-name> <chart> renders the chart with the values you pass and prints the resulting YAML to stdout — no cluster call, no release record. Adding -s/--show-only templates/job.yaml limits the output to that one template file's manifests.

open as a page

In a Helm chart template, how do the `default` and `required` functions differ?

level: juniorimportance: must knowfreq 76%

basics

~20 s

Helm's default function substitutes a fallback when a values entry is empty, so the render continues. required does the opposite: it aborts the whole render with the message you wrote. Use default for optional settings, required where no safe fallback exists.

open as a page

In a Helm chart, what is the difference between `{{ include "x" . }}` and `{{ template "x" . }}`?

level: juniorimportance: must knowfreq 70%

basics

~20 s

include is a function Helm adds: it renders a named template and returns the output as a string, so it can be piped into other functions. template is a Go template action that writes straight to the output and yields no value.

open as a page

In a helm install, what wins when values.yaml, two -f files and --set all set the same key?

level: juniorimportance: must knowfreq 85%

basics

~20 s

Helm starts from the chart's own values.yaml, merges each -f file in the order given so a later file beats an earlier one, and applies the --set family last. A --set value therefore wins over every file.

open as a page

What does `helm upgrade` do with the previous release's values when neither --reuse-values nor --reset-values is passed?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Helm's default is conditional: with no -f or --set on the upgrade, the previous release's user-supplied values carry forward. Pass even one value flag and those stored overrides are dropped, leaving only your new flags over the chart's defaults.

open as a page

In a Helm chart template, what do the `{{-` and `-}}` trim markers do to the rendered YAML?

level: juniorimportance: must knowfreq 76%

basics

~20 s

A leading {{- deletes every whitespace character before the action, including the newline that ended the previous line, and a trailing -}} deletes the whitespace after it, including its own newline. Chart authors use them so control-flow lines leave no blank lines in the rendered YAML.

open as a page

In a Helm chart, how do `.Capabilities.APIVersions.Has` and `.Capabilities.KubeVersion` decide which apiVersion a template emits?

level: middleimportance: must knowfreq 62%

basics

~10 s

Helm reads the target cluster's discovery list and reported server version before rendering. .Capabilities.APIVersions.Has answers whether that cluster serves a given group/version, and .Capabilities.KubeVersion.Version gives its version string for a semver comparison.

open as a page

In Helm 4, how does helm template differ from helm install --dry-run=client and --dry-run=server?

level: middleimportance: must knowfreq 64%

basics

~20 s

helm template renders offline and prints YAML. helm install --dry-run=client renders through the install path against your configured cluster but writes nothing. --dry-run=server also sends the manifests to the API server, so schema validation and admission run and nothing is persisted.

open as a page

Why does a Helm values override replace a whole list instead of merging into it?

level: middleimportance: must knowfreq 65%

basics

~20 s

Helm deep-merges maps key by key, but treats a YAML list as one opaque value: an override that supplies a list replaces the chart's list entirely. List elements have no key to match on, so nothing can be merged or appended.

open as a page

How do `--reuse-values` and `--reset-then-reuse-values` differ on a Helm upgrade to a newer chart version?

level: middleimportance: must knowfreq 58%

basics

~20 s

--reuse-values merges your new flags over the previous release's fully coalesced values, which include the old chart's defaults, so a default changed in the new chart is masked. --reset-then-reuse-values merges over only the old user-supplied overrides, letting new defaults through.

open as a page

Inside a Helm named template invoked as `include "app.labels" .Values.worker`, what does `$` refer to?

level: middleimportance: must knowfreq 66%

basics

~20 s

$ is the root of the current template execution, and every include or template call starts a new one. Inside that helper $ is .Values.worker, the argument it was handed — so neither dot nor $ can reach .Values or .Release there.

open as a page

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

level: middleimportance: must knowfreq 70%

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.

open as a page

In a Helm chart, why does a `{{- with .Values.podAnnotations }}` block render nothing when that value is `{}`?

level: juniorimportance: should knowfreq 58%

basics

~20 s

with guards as well as re-scopes. An empty map is false, so Helm skips the whole block and every line inside it vanishes from the rendered manifest, silently. Inside the block dot is that map, so .Values no longer resolves.

open as a page

In a Helm chart, what do .Release.IsInstall, .Release.IsUpgrade and .Release.Revision report?

level: middleimportance: should knowfreq 57%

basics

~20 s

They describe the operation currently rendering. On install, IsInstall is true, IsUpgrade is false and Revision is 1. On upgrade the first two swap and Revision is the number of the revision being created, one higher than the current one.

open as a page

Why does a Helm chart fail to render with `function "env" not defined` when sprig documents an env function?

level: middleimportance: should knowfreq 41%

basics

~20 s

Helm registers the sprig function set but deliberately deletes env and expandenv, and Go templates reject unknown function names while parsing. A chart therefore cannot read the environment of whoever runs helm; caller-side data must arrive as values.

open as a page

In a Helm chart template, what do `toYaml`, `fromYaml` and `toJson` do?

level: middleimportance: should knowfreq 58%

basics

~20 s

They convert between structured values and text inside a Helm template. toYaml serialises any values subtree to a YAML string the caller must indent; fromYaml parses a YAML string back into a map; toJson emits compact JSON, which is also valid YAML.

open as a page

A Helm named template needs two inputs, but `include` takes one argument — how do you pass both?

level: middleimportance: should knowfreq 48%

basics

~20 s

Bundle the inputs into one value with the dict function and pass that: include "fraud-scoring.probe" (dict "ctx" $ "port" .Values.service.port). Inside the partial, dot is the dict, so the inputs are read as .ctx and .port.

open as a page

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

level: middleimportance: should knowfreq 41%

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 . }}.

open as a page

When does Helm's --set mangle a value, and which --set-* variant fixes it?

level: middleimportance: should knowfreq 50%

basics

~20 s

Helm's --set infers types and treats dots, commas and brackets as syntax, so an image tag written 1.10 becomes the number 1.1. Use --set-string for text, --set-file for file contents, --set-json for structure, --set-literal for a verbatim value.

open as a page

Which Helm built-in objects are safe to build a Kubernetes resource name from?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Only the ones that do not change between upgrades: .Release.Name, .Chart.Name and stable values. .Chart.Version, .Chart.AppVersion, .Release.Revision and .Release.IsInstall all move, and a moved name makes Helm create a new object and delete the old one.

open as a page

A CI job runs `helm template` and emits a different apiVersion than the cluster install does — why, and how do you fix the render?

level: seniorimportance: should knowfreq 44%

basics

~20 s

With no cluster to query, helm template uses capabilities compiled into the Helm binary — a fixed Kubernetes version and a minimal API list — so capability branches take their fallback path. Pass --kube-version and --api-versions to describe the target cluster.

open as a page

A Helm chart fails to render with `nil pointer evaluating interface {}.tag` — how do you find the cause?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Read the error, not the YAML: it names the chart's template file, a line and column, and the failing expression in an at <...> clause. A nil pointer means the parent key was absent, so re-render locally with the same values flags.

open as a page

After `helm upgrade --reuse-values`, a key you deleted from your values file still renders — why?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Deleting a key from your file only removes it from what you pass; --reuse-values merges the previous release's stored values underneath, so it comes back. helm get values prints those stored overrides; helm get values -a prints everything computed.

open as a page

After a Helm upgrade, one tenant's worker Deployment lost its entire tolerations block while other tenants kept theirs — how do you diagnose it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Compare the manifests stored with the two revisions: the block is absent, not empty, which means a guard collapsed on a falsy value in that tenant's overrides. Helm then withdrew the field, because the new manifest no longer declares it.

open as a page

A `helm upgrade` fails with `error converting YAML to JSON: yaml: line 41: did not find expected key`. How do you find the cause?

level: seniorimportance: should knowfreq 50%

basics

~20 s

The line number indexes the rendered manifest, not the template file Helm names in the same message. Render the chart with the failing release's values, count to that line, and look at it and the line above for a value spliced in at an indentation that does not match its siblings.

open as a page

How do you decide between Chart.yaml `kubeVersion` and `.Capabilities` branching for a chart installed across many cluster versions?

level: principalimportance: should knowfreq 36%

basics

~20 s

kubeVersion in Chart.yaml is a hard semver gate: Helm refuses to install outside the range. .Capabilities branching keeps one chart usable across a span at the cost of permutations nobody tests. Gate the floor; branch only where behaviour genuinely differs.

open as a page

How would you standardise what a `helm upgrade` starts from across many teams and pipelines?

level: principalimportance: should knowfreq 34%

basics

~20 s

Make every automated upgrade pass the complete values file and forbid reuse flags there, so the repository is the only description of a release. Allow --reset-then-reuse-values for interactive triage, and audit releases by diffing their stored values against the committed file.

open as a page

What can a Helm chart read with .Files.Get and .Files.Glob, and what is invisible to them?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

.Files reads non-template files packaged inside the chart, by path relative to the chart root. Get returns one file's contents as a string, Glob returns a set. Files under templates/ and anything .helmignore excluded are invisible, and a missing path returns an empty string rather than an error.

open as a page

showing 1–30 of 35