Lesson 13 Helpers & Named Templates
Theme:
Lesson 13 · Slide 1

Helpers & Named Templates

Eliminate repetition in your Helm charts with _helpers.tpl, define/include, and reusable template snippets that keep your manifests DRY.

Learning Objectives

Lesson 13 · Slide 2

_helpers.tpl — The Convention File

Any file in templates/ that starts with an underscore is never rendered as a Kubernetes manifest. By convention, reusable template snippets live in _helpers.tpl.

Define a Named Template

{{/* templates/_helpers.tpl */}}

{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}

Use It in a Template

metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
Notes: The {{- ... -}} dash syntax trims surrounding whitespace, which is critical for clean YAML output.
Lesson 13 · Slide 3

include vs template

Go provides two ways to invoke named templates. In Helm charts, always prefer include because it allows pipelines.

template (built-in)

{{ template "mychart.labels" . }}
{{/* Cannot pipe output! */}}
{{/* This FAILS: */}}
{{ template "mychart.labels" . | nindent 4 }}

template writes directly to output — it returns nothing, so you cannot pipe the result through functions like nindent or toYaml.

include (Helm function)

{{ include "mychart.labels" . | nindent 4 }}
{{/* Works! include returns a string */}}
{{ include "mychart.annotations" . | indent 8 }}

include captures the output as a string, so you can pipe it into nindent, toYaml, trim, and any other Sprig function.

Notes: include is a Helm-specific function, not part of core Go templates. It exists precisely to solve the pipeline limitation of template.
Lesson 13 · Slide 4

Standard Helpers from helm create

Running helm create mychart generates a _helpers.tpl with six standard helpers. Here is what each one does:

HelperPurpose
mychart.nameChart name, truncated to 63 chars (DNS label limit)
mychart.fullnameRelease-name + chart-name, respects fullnameOverride
mychart.chartname-version string for the helm.sh/chart label
mychart.labelsStandard set of metadata labels (app, version, managed-by, etc.)
mychart.selectorLabelsMinimal labels for Deployment/Service selectors (name + instance)
mychart.serviceAccountNameConditional: use override or fall back to fullname

Why Two Label Helpers?

selectorLabels must be immutable after creation (Kubernetes rejects selector changes on Deployments). The full labels helper includes selectorLabels plus informational labels like app.kubernetes.io/version that can change on upgrades.

Lesson 13 · Slide 5

Writing Custom Helpers

You can add any helper you need. Two common patterns:

Image Pull Secret Helper

{{- define "mychart.imagePullSecrets" -}}
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.imagePullSecrets }}
  - name: {{ . }}
{{- end }}
{{- end }}
{{- end }}

Annotation Merge Helper

{{- define "mychart.annotations" -}}
{{- $base := dict "app.kubernetes.io/managed-by" "Helm" -}}
{{- $extra := .Values.commonAnnotations | default dict -}}
{{- toYaml (merge $extra $base) -}}
{{- end }}

Usage in a template:

metadata:
  annotations:
    {{- include "mychart.annotations" . | nindent 4 }}
spec:
  {{- include "mychart.imagePullSecrets" . | nindent 2 }}
Lesson 13 · Slide 6

Scope and the Dot (.)

Every template executes with a scope — the value of . (dot). When you call include, the second argument sets the scope for the called template.

{{/* Pass full context */}}
{{ include "mychart.labels" . }}

{{/* Pass only .Values.app */}}
{{ include "mychart.appConfig" .Values.app }}

{{/* Pass a custom dict */}}
{{ include "mychart.resource" (dict "name" "cpu" "limit" "500m") }}

Common Pitfall

Inside a range loop, . is rebound to the current item. To access the root context, use $:

{{- range .Values.services }}
  name: {{ .name }}
  chart: {{ include "mychart.chart" $ }}
  {{/* $ always points to root scope */}}
{{- end }}
Lesson 13 · Slide 7

DRY Patterns & Overriding Helpers

Named templates are the primary DRY mechanism in Helm. Two advanced patterns:

Extracting Repeated YAML

{{- define "mychart.probes" -}}
livenessProbe:
  httpGet:
    path: {{ .path | default "/healthz" }}
    port: {{ .port | default "http" }}
  initialDelaySeconds: 10
readinessProbe:
  httpGet:
    path: {{ .path | default "/readyz" }}
    port: {{ .port | default "http" }}
{{- end }}

Overriding in Parent Charts

When chart B depends on chart A, B can redefine any of A's helpers:

{{/* In parent chart B's _helpers.tpl */}}
{{- define "chartA.fullname" -}}
{{- printf "custom-%s" .Release.Name -}}
{{- end }}

The last definition wins. Parent charts load after subcharts, so the parent's version takes precedence.

Notes: Prefix helper names with the chart name (e.g., mychart.labels) to avoid collisions when charts are used as subcharts.
Lesson 13 · Slide 8

Lab & Homework

Lab Exercises

  1. Inspect the default _helpers.tpl from helm create
  2. Write custom helpers for image pull secrets and health probes
  3. Refactor a chart with duplicated YAML into DRY named templates

Homework Tasks

  1. Build a complete _helpers.tpl from scratch with 4+ helpers
  2. Create a helper that merges annotations from multiple sources
  3. Demonstrate helper overriding in a parent/subchart relationship
1 / ?