Lesson 13 Helpers & Named Templates
Theme:

Lab Guide

Explore the default _helpers.tpl, write custom helpers for labels and image references, and refactor duplicated YAML into named templates.

Prerequisites

Task 1: Explore Default _helpers.tpl

Step 1 — Scaffold a chart

helm create myapp
cd myapp

Step 2 — Read the generated helpers

Open the helpers file and study each named template:

cat templates/_helpers.tpl

You should see six define blocks. Identify each one:

Step 3 — See how helpers are invoked

Check where include is used across the templates:

grep -n 'include' templates/*.yaml

Notice that every template file references helpers via include (not template), and most pipe the result into nindent for proper YAML indentation.

Step 4 — Render and trace

Render the chart and observe the helpers in action:

helm template testrelease . --set nameOverride=webapp

Search the output for the generated names and labels:

helm template testrelease . --set nameOverride=webapp | grep "app.kubernetes.io"

You should see labels like app.kubernetes.io/name: webapp and app.kubernetes.io/instance: testrelease consistently applied across Deployment, Service, and ServiceAccount resources.

Step 5 — Test fullnameOverride

helm template testrelease . --set fullnameOverride=my-custom-name | grep "name:"

All resource names should use my-custom-name instead of the default testrelease-myapp pattern.

✓ Check: You can identify all six default helpers and trace their usage across the rendered output.

Task 2: Write Custom Helpers

Step 1 — Add an image reference helper

Image references typically combine a registry, repository, and tag. Instead of repeating this logic in every container spec, create a helper. Add the following to the bottom of templates/_helpers.tpl:

{{/*
Build a fully qualified image reference.
Usage: {{ include "myapp.image" (dict "image" .Values.image "root" .) }}
*/}}
{{- define "myapp.image" -}}
{{- $registry := .image.registry | default "" -}}
{{- $repository := .image.repository -}}
{{- $tag := .image.tag | default .root.Chart.AppVersion -}}
{{- if $registry -}}
  {{- printf "%s/%s:%s" $registry $repository $tag -}}
{{- else -}}
  {{- printf "%s:%s" $repository $tag -}}
{{- end -}}
{{- end }}

Step 2 — Update values.yaml

Modify the image section in values.yaml to include a registry field:

image:
  registry: ""
  repository: nginx
  pullPolicy: IfNotPresent
  tag: ""

Step 3 — Use the helper in the Deployment

Edit templates/deployment.yaml and replace the existing image line. Find the line that reads:

          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"

Replace it with:

          image: "{{ include "myapp.image" (dict "image" .Values.image "root" .) }}"

Step 4 — Test with different configurations

# Default (no registry, uses appVersion as tag)
helm template testrelease . | grep "image:"

# With explicit tag
helm template testrelease . --set image.tag=1.25-alpine | grep "image:"

# With private registry
helm template testrelease . \
  --set image.registry=registry.example.com \
  --set image.tag=v2.0.0 | grep "image:"

Expected output for each:
image: "nginx:1.16.0" (default appVersion)
image: "nginx:1.25-alpine" (explicit tag)
image: "registry.example.com/nginx:v2.0.0" (with registry)

Step 5 — Add a common-labels helper

Sometimes you need extra labels beyond the standard set. Add a helper that merges standard labels with custom ones from values:

{{/*
Common labels: standard labels + any user-defined extra labels.
*/}}
{{- define "myapp.commonLabels" -}}
{{- include "myapp.labels" . }}
{{- with .Values.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end }}

Step 6 — Test the common-labels helper

Replace include "myapp.labels" . with include "myapp.commonLabels" . in the Deployment metadata. Then render:

helm template testrelease . \
  --set commonLabels.team=platform \
  --set commonLabels.cost-center=eng-42 | grep -A 10 "labels:"

✓ Check: You should see the standard Helm labels plus team: platform and cost-center: eng-42.

Task 3: Refactor Duplicated YAML into Named Templates

Step 1 — Create a second workload

Create a new template templates/worker-deployment.yaml for a background worker that reuses the same image, labels, and configuration:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}-worker
  labels:
    {{- include "myapp.commonLabels" . | nindent 4 }}
    app.kubernetes.io/component: worker
spec:
  replicas: {{ .Values.worker.replicaCount | default 1 }}
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
      app.kubernetes.io/component: worker
  template:
    metadata:
      labels:
        {{- include "myapp.selectorLabels" . | nindent 8 }}
        app.kubernetes.io/component: worker
    spec:
      containers:
        - name: worker
          image: "{{ include "myapp.image" (dict "image" .Values.image "root" .) }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          command: ["nginx", "-g", "daemon off;"]
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

Step 2 — Identify the duplication

Compare the two Deployment files. The resources, securityContext, and container probe blocks are likely identical. Let us extract the resource limits into a helper.

Step 3 — Create a resource-limits helper

Add to templates/_helpers.tpl:

{{/*
Default resource limits. Pass a dict with "resources" key.
Falls back to chart-level .Values.resources if not provided.
*/}}
{{- define "myapp.resources" -}}
{{- $res := .resources | default dict -}}
{{- if $res }}
{{- toYaml $res }}
{{- end }}
{{- end }}

Step 4 — Create a pod-security helper

Extract the common security context:

{{/*
Standard security context for all containers.
*/}}
{{- define "myapp.securityContext" -}}
securityContext:
  runAsNonRoot: true
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
{{- end }}

Step 5 — Use the helpers in both Deployments

In both templates/deployment.yaml and templates/worker-deployment.yaml, replace inline resource and security blocks with the helpers:

          {{- include "myapp.securityContext" . | nindent 10 }}
          resources:
            {{- include "myapp.resources" (dict "resources" .Values.resources) | nindent 12 }}

Step 6 — Add worker values and render

Add worker configuration to values.yaml:

worker:
  replicaCount: 2

Render the full chart and verify both Deployments share the same helpers:

helm template testrelease . | grep -B2 -A6 "securityContext"

Both the main Deployment and the worker Deployment should show identical securityContext blocks produced by the myapp.securityContext helper.

Step 7 — Verify with helm lint

helm lint .

Ensure no errors or warnings are reported. If there are warnings about missing values, add sensible defaults in values.yaml.

Step 8 — Install and confirm

helm install lab13 . --namespace lab13 --create-namespace
kubectl get deployments -n lab13

✓ Check: You should see two Deployments: lab13-myapp and lab13-myapp-worker.

Cleanup

helm uninstall lab13 --namespace lab13
kubectl delete namespace lab13
cd .. && rm -rf myapp

Summary