Deep Dive into Helm Templates
Introduction
Helm templates are Kubernetes manifests augmented with Go’s text/template language. They live in the templates/ directory of your chart and are rendered by Helm using values from values.yaml and any user-provided overrides. Understanding them is key to writing maintainable, reusable charts.
Template Basics
A minimal Deployment template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-app
labels:
app: {{ .Chart.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Chart.Name }}
template:
metadata:
labels:
app: {{ .Chart.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
Built-in Objects
| Object | Description |
|---|---|
.Release | Current release info: .Release.Name, .Release.Namespace |
.Chart | Contents of Chart.yaml: .Chart.Name, .Chart.Version |
.Values | Merged values from values.yaml and user overrides |
.Files | Access to non-template files bundled in the chart |
Template Functions
Helm bundles Go’s Sprig library with over 60 helper functions:
# Default value when empty
image: {{ .Values.image | default "nginx:latest" }}
# Quote a string safely
annotation: {{ .Values.description | quote }}
# Convert to uppercase
name: {{ .Values.name | upper }}
# Format strings
fullname: {{ printf "%s-%s" .Release.Name .Chart.Name }}
# Indent a multi-line block (critical for nested YAML)
resources:
{{- toYaml .Values.resources | nindent 2 }}
Conditionals
spec:
{{- if .Values.resources }}
resources:
{{- toYaml .Values.resources | nindent 4 }}
{{- end }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{- toYaml .Values.nodeSelector | nindent 4 }}
{{- else }}
nodeSelector: {}
{{- end }}
The - in {{- strips leading whitespace; -}} strips trailing whitespace. This is essential for producing clean YAML output.
Loops with range
Iterate over a list:
env:
{{- range .Values.envVars }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
Iterate over a map:
annotations:
{{- range $key, $val := .Values.annotations }}
{{ $key }}: {{ $val | quote }}
{{- end }}
Named Templates
Named templates avoid repetition across files. By convention they live in _helpers.tpl (the _ prefix prevents Helm from rendering the file as a manifest):
{{/* _helpers.tpl */}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
Include them in other templates with include:
metadata:
labels:
{{- include "myapp.labels" . | nindent 4 }}
Always prefer
includeover the built-intemplateaction becauseincludereturns a string that can be piped to functions likenindent.
Debugging Templates
Render templates locally without deploying to the cluster:
# Render all templates
helm template my-release ./my-chart
# Render with custom values
helm template my-release ./my-chart -f custom-values.yaml
# Validate and lint
helm lint ./my-chart
To inspect a rendered object during development, temporarily dump it to YAML:
{{- toYaml .Values | nindent 0 }}
If you want to support me, buy me a coffee.