Lesson 3 · Helm fundamentals
Templates & the Go engine
How {{ .Values.x }} becomes YAML — the objects, functions, and pipelines you'll actually read.
Your win: read a Helm template — the built-in objects
(.Values / .Release / .Chart / .Capabilities),
functions and pipelines, and the whitespace/indent idioms — using real lines from this repo.
Templates are Go templates that emit YAML
Each file in templates/ is a Go text/template. Actions live in
{{ }}; everything else is passed through literally. Helm renders each file, then
hands the resulting YAML to Kubernetes.1
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "util.fullname" . }} labels: {{- include "util.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount | default 2 }}
Built-in objects — the data you template from
| Object | Holds |
|---|---|
.Values | the merged values (defaults + overrides) |
.Release | .Name, .Namespace, .IsUpgrade, .Revision |
.Chart | the Chart.yaml fields (.Name, .Version) |
.Capabilities | cluster/API info — e.g. .APIVersions.Has "keda.sh/v1alpha1" |
.Files | access to non-template files in the chart |
.Capabilities: libs/util/templates/_app.tpl:39 —{{- if .Capabilities.APIVersions.Has "keda.sh/v1alpha1" }}chooses KEDA vs HPA based on what's installed in the cluster (Course 2 L9)..Release.Namespace: _keda.tpl:152 — baked into the Prometheus query so metrics are scoped to the release's namespace..Values.globalfallback: _statefulset.tpl:12 —replicas: {{ default .Values.global.replicaCount .Values.replicaCount }}..Files+tpl: _configmap.tpl:22 —tpl (printf "configs/%s.common.config.yaml" .Chart.Name | .Files.Get) .reads a config file and renders it as a template.
Functions & pipelines
Helm ships Go template functions plus the Sprig library. You chain them
with pipelines (|), left to right:2
| Idiom | Does |
|---|---|
{{ .Values.x | default "y" }} | fall back if unset |
{{ toYaml .Values.resources | nindent 4 }} | serialize a block & indent it |
{{ include "t" . | nindent 4 }} | render a named template, indent |
{{ tpl $str . }} | render a string as a template |
{{ required "msg" .Values.x }} | fail the render if unset |
{{- trims preceding whitespace,
-}} trailing — essential because YAML is indentation-sensitive.
toYaml | nindent N: turn a values block into YAML and indent it
to the right column. Get nindent wrong and you get an invalid-YAML error — the
single most common Helm bug.
fromYaml (include "_util.deployment.spot" .) then
mustMergeOverwrite with an on-demand override and toYaml back out —
i.e. it renders one Deployment, deep-merges a patch, and re-emits it (Course 2 L10's
spot/on-demand nodes). For validation it prefers fail over required
(_check.tpl:44).
include vs template — pick include
Both render a named template. include returns a string you can pipe
(| nindent 4); template is a bare action that can't be
piped. That's why every call here is include.
Helm docs — Built-in Objects + Functions and Pipelines
The full object list, the function/Sprig reference, and the whitespace & indent rules.
→ helm.sh — Built-in Objects
→ helm.sh — Functions & Pipelines
Check yourself (from memory)
Q1. .Release.Namespace gives you…
.Release.* describes the install; the repo bakes
it into the KEDA Prometheus query to scope metrics.
Q2. toYaml .Values.resources | nindent 4 is used to…
Q3. You prefer include over template because…
include returns a string you can pipe;
template is an action that can't be piped.
{{ }}.
BUILT-IN OBJECTS: .Values (merged config), .Release
(Name/Namespace/IsUpgrade/Revision), .Chart (Chart.yaml fields),
.Capabilities (e.g. .APIVersions.Has "keda.sh/v1alpha1"),
.Files. FUNCTIONS/PIPELINES (Go + Sprig): default, toYaml |
nindent N (serialize + indent — wrong indent = the classic bug), tpl
(render a string as a template), required/fail,
fromYaml/mustMergeOverwrite. Whitespace: {{- /
-}} trim. INCLUDE vs TEMPLATE: include returns a pipeable string
(use it); template can't be piped. Repo idiom: fromYaml→mustMergeOverwrite→toYaml
for spot/on-demand merge (_deployment.tpl:23-25).if/range/with and scoping (the
. context), or debug an nindent mistake together?
Ask me.