TT Lab

한국어 English 日本語

Get started
Blog

Blog

Helm Template Engine Deep Dive: Go Templates, Sprig, Named Templates


1. Go Template Internals

1.1 Parsing Stage

Helm's template engine is built on Go's text/template standard library. Template processing is divided into parsing and execution stages.

The parsing stage transforms template text into an AST (Abstract Syntax Tree):

Template text -> Lexer -> Token stream -> Parser -> AST

1.2 Execution Stage

The execution stage traverses the AST while applying the data context (dot .):

AST + Data context -> Rendered text output

The Pipeline concept is key:

# Pipeline: left output is passed as right input
name: { { .Values.name | lower | trunc 63 | trimSuffix "-" } }

# Execution order:
# 1. Evaluate .Values.name -> "My-Application-Name"
# 2. Apply lower -> "my-application-name"
# 3. Apply trunc 63 -> "my-application-name"
# 4. Apply trimSuffix "-" -> "my-application-name"

1.3 Whitespace Control

# Hyphens (-) remove whitespace
metadata:
  labels:
    {{- include "my-chart.labels" . | nindent 4 }}
    #^ removes left whitespace/newlines

    {{ include "my-chart.labels" . | nindent 4 -}}
    #                                           ^ removes right whitespace/newlines

    {{- include "my-chart.labels" . | nindent 4 -}}
    #^ removes both sides

2. Sprig Library Functions

2.1 String Functions

upper: {{ "hello" | upper }}          # HELLO
lower: {{ "HELLO" | lower }}          # hello
title: {{ "hello world" | title }}    # Hello World
trim: {{ "  hello  " | trim }}        # hello
substr: {{ substr 0 5 "hello world" }}# hello
trunc: {{ "hello" | trunc 3 }}        # hel
contains: {{ contains "lo" "hello" }} # true
replace: {{ "hello world" | replace " " "-" }}  # hello-world
snakecase: {{ "HelloWorld" | snakecase }}        # hello_world
camelcase: {{ "hello_world" | camelcase }}       # HelloWorld
kebabcase: {{ "HelloWorld" | kebabcase }}        # hello-world
regexMatch: {{ regexMatch "^[a-z]+$" "hello" }} # true
randAlphaNum: {{ randAlphaNum 10 }}    # 10-char random alphanumeric

2.2 Math Functions

add: { { add 1 2 } } # 3
sub: { { sub 10 3 } } # 7
mul: { { mul 2 3 } } # 6
div: { { div 10 3 } } # 3
mod: { { mod 10 3 } } # 1
max: { { max 1 2 3 } } # 3
min: { { min 1 2 3 } } # 1
ceil: { { ceil 1.1 } } # 2
floor: { { floor 1.9 } } # 1
round: { { round 1.5 0 } } # 2

2.3 Date Functions

now: { { now } }
date: { { now | date "2006-01-02" } }
dateModify: { { now | dateModify "+24h" | date "2006-01-02" } }

2.4 Crypto Functions

sha256sum: {{ "hello" | sha256sum }}
b64enc: {{ "hello" | b64enc }}          # aGVsbG8=
b64dec: {{ "aGVsbG8=" | b64dec }}       # hello
uuid: {{ uuidv4 }}

2.5 List Functions

list: { { list "a" "b" "c" } }
first: { { first (list "a" "b" "c") } } # a
last: { { last (list "a" "b" "c") } } # c
append: { { append (list "a" "b") "c" } } # [a b c]
has: { { has "b" (list "a" "b" "c") } } # true
without: { { without (list "a" "b" "c") "b" } } # [a c]
uniq: { { uniq (list "a" "b" "a") } } # [a b]
sortAlpha: { { sortAlpha (list "c" "a" "b") } } # [a b c]

2.6 Dictionary Functions

{{- $myDict := dict "key1" "value1" "key2" "value2" }}
get: {{ get $myDict "key1" }}              # value1
hasKey: {{ hasKey $myDict "key1" }}         # true
keys: {{ keys $myDict }}                    # [key1 key2]
values: {{ values $myDict }}                # [value1 value2]
{{- $merged := merge (dict "a" "1") (dict "b" "2") }}

3. Named Templates

3.1 define and template

# templates/_helpers.tpl
{{- define "my-chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

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

3.2 template vs include

# template: outputs directly (no pipeline)
{{ template "my-chart.name" . }}

# include: returns as string (pipeline available)
{{ include "my-chart.labels" . | nindent 4 }}

include is recommended because it supports post-processing via pipelines (indentation, etc.).

3.3 _helpers.tpl Convention

# Standard helper patterns
{{- define "my-chart.labels" -}}
helm.sh/chart: {{ include "my-chart.chart" . }}
{{ include "my-chart.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "my-chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

4. Flow Control

4.1 if/else

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "my-chart.fullname" . }}
{{- end }}

{{- if eq .Values.service.type "NodePort" }}
  nodePort: {{ .Values.service.nodePort }}
{{- else if eq .Values.service.type "LoadBalancer" }}
  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}

Falsy values: false, 0, empty string "", nil, empty collections

4.2 with

{{- with .Values.nodeSelector }}
nodeSelector:
  {{- toYaml . | nindent 2 }}
{{- end }}

# Access parent scope with $ inside with blocks
{{- with .Values.container }}
  name: {{ $.Chart.Name }}
  image: {{ .image }}
{{- end }}

4.3 range

# List iteration
{{- range .Values.extraEnvVars }}
- name: {{ .name }}
  value: {{ .value | quote }}
{{- end }}

# Map iteration
{{- range $key, $value := .Values.configData }}
{{ $key }}: {{ $value | quote }}
{{- end }}

# Fixed count
{{- range until 5 }}
- replica-{{ . }}
{{- end }}

5. Advanced Template Patterns

5.1 toYaml and nindent

spec:
  template:
    spec:
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

5.2 tpl Function

# values.yaml
configTemplate: |
  server.name={{ .Release.Name }}

# templates/configmap.yaml
data:
  config.properties: |
    {{ tpl .Values.configTemplate . | nindent 4 }}

5.3 lookup Function

{{- $secret := lookup "v1" "Secret" .Release.Namespace "my-secret" }}
{{- if $secret }}
password: {{ index $secret.data "password" }}
{{- else }}
password: {{ randAlphaNum 16 | b64enc }}
{{- end }}

Note: lookup always returns empty results with helm template.


6. Library Charts

Library charts (type: library) render no resources themselves, only provide named templates:

# Chart.yaml
apiVersion: v2
name: my-library
type: library
version: 1.0.0

Benefits: shared template logic, centralized label/annotation standards, DRY compliance.


7. Debugging

# Local template rendering
helm template my-release ./my-chart
helm template my-release ./my-chart -s templates/deployment.yaml
helm template my-release ./my-chart --debug

# Lint
helm lint ./my-chart --strict

8. Summary

The Helm template engine provides a powerful programming model beyond simple string substitution:

  1. Go template-based: Two-stage parsing-execution pipeline architecture
  2. Sprig library: Over 100 utility functions
  3. Named templates: Modular, reusable templates with define/include
  4. Flow control: Conditional and iterative rendering with if/with/range
  5. Library charts: Centralized management and sharing of common logic

The next post analyzes the complete Helm release lifecycle (install, upgrade, rollback).

Sign in to like

Comments

No comments yet.

Sign in to leave a comment