Introduction
Helm templates use Go's text/template engine to render Kubernetes manifests dynamically. Variables come from values.yaml (defaults), --set flags (overrides), and custom values files (-f custom.yaml). Passing multiple variables involves accessing them through the .Values object in templates, using --set for individual overrides, and using custom values files for environment-specific configurations. For passing variables between templates and helper functions, Helm uses the dict, list, and include functions.
Passing Variables via values.yaml
1# values.yaml
2replicaCount: 3
3image:
4 repository: nginx
5 tag: "1.25"
6 pullPolicy: IfNotPresent
7service:
8 type: ClusterIP
9 port: 80
10database:
11 host: postgres.default.svc
12 port: 5432
13 name: myapp
14 credentials:
15 username: admin
16 password: secret123
1# templates/deployment.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: {{ .Release.Name }}-app
6spec:
7 replicas: {{ .Values.replicaCount }}
8 template:
9 spec:
10 containers:
11 - name: app
12 image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
13 imagePullPolicy: {{ .Values.image.pullPolicy }}
14 ports:
15 - containerPort: {{ .Values.service.port }}
16 env:
17 - name: DB_HOST
18 value: {{ .Values.database.host | quote }}
19 - name: DB_PORT
20 value: {{ .Values.database.port | quote }}
21 - name: DB_NAME
22 value: {{ .Values.database.name | quote }}
Overriding with --set
1# Override single values
2helm install myrelease ./mychart --set replicaCount=5
3
4# Override nested values with dot notation
5helm install myrelease ./mychart \
6 --set image.tag="2.0" \
7 --set database.host="db.prod.svc"
8
9# Override multiple values in one --set
10helm install myrelease ./mychart \
11 --set replicaCount=5,service.port=8080
12
13# Set string values explicitly (useful for numeric strings)
14helm install myrelease ./mychart --set-string image.tag="1.25"
15
16# Override with array values
17helm install myrelease ./mychart \
18 --set "ingress.hosts[0].host=example.com" \
19 --set "ingress.hosts[0].paths[0].path=/"
Using Custom Values Files
1# values-production.yaml
2replicaCount: 5
3image:
4 tag: "1.25-stable"
5database:
6 host: postgres-prod.database.svc
7 credentials:
8 username: prod_admin
9resources:
10 requests:
11 memory: 512Mi
12 cpu: 500m
13 limits:
14 memory: 1Gi
15 cpu: 1000m
1# Use default values.yaml + production overrides
2helm install myrelease ./mychart -f values-production.yaml
3
4# Stack multiple values files (later files override earlier ones)
5helm install myrelease ./mychart \
6 -f values.yaml \
7 -f values-production.yaml \
8 -f values-secrets.yaml
9
10# Combine files with --set (--set takes highest priority)
11helm install myrelease ./mychart \
12 -f values-production.yaml \
13 --set image.tag="hotfix-1.25.1"
Passing Variables Between Templates
Using include with Context
1# templates/_helpers.tpl
2{{- define "mychart.labels" -}}
3app.kubernetes.io/name: {{ .name }}
4app.kubernetes.io/instance: {{ .release }}
5app.kubernetes.io/version: {{ .version }}
6{{- end }}
1# templates/deployment.yaml
2metadata:
3 labels:
4 {{- include "mychart.labels" (dict "name" .Chart.Name "release" .Release.Name "version" .Chart.AppVersion) | nindent 4 }}
Using dict to Pass Multiple Variables
1# Pass a custom dictionary to a template
2{{- define "mychart.container" -}}
3- name: {{ .name }}
4 image: "{{ .image }}:{{ .tag }}"
5 ports:
6 - containerPort: {{ .port }}
7{{- end }}
8
9# Call it with a dict
10containers:
11 {{- include "mychart.container" (dict "name" "web" "image" .Values.image.repository "tag" .Values.image.tag "port" 8080) | nindent 8 }}
Using $ to Access Root Context
Inside range loops, the context (.) changes. Use $ to access the root context.
1# templates/configmap.yaml
2apiVersion: v1
3kind: ConfigMap
4metadata:
5 name: {{ $.Release.Name }}-config
6data:
7 {{- range $key, $value := .Values.config }}
8 {{ $key }}: {{ $value | quote }}
9 {{- end }}
Variable Assignment in Templates
1# Assign variables with $varName
2{{- $fullName := printf "%s-%s" .Release.Name .Chart.Name -}}
3{{- $port := .Values.service.port | default 80 -}}
4
5metadata:
6 name: {{ $fullName }}
7spec:
8 ports:
9 - port: {{ $port }}
Common Pitfalls
Forgetting to quote string values: YAML interprets unquoted values like true, false, null, and numbers automatically. Use {{ .Values.tag | quote }} or --set-string to ensure values are treated as strings. Without quoting, tag: "1.0" becomes the float 1 in YAML.
--set priority confusion: Values cascade in order: values.yaml (lowest) < -f custom.yaml < --set (highest). Later sources override earlier ones. This means --set always wins, which can be confusing when debugging why a values file override is not taking effect.
Losing root context in range loops: Inside {{ range .Values.items }}, the dot (.) refers to the current item, not the root context. Accessing .Release.Name inside the loop fails. Use $.Release.Name to access the root context.
Not using nindent for included templates: include returns the template output as a string. Without | nindent N, the included YAML is not properly indented, causing invalid Kubernetes manifests. Always pipe to nindent with the correct indentation level.
Passing the wrong context to include: include "mychart.labels" . passes the current context. If called inside a range loop, . is the loop item, not the root. Use include "mychart.labels" $ or construct a dict with the specific values needed.
Summary
Access variables in templates via {{ .Values.key }} from values.yaml
Override with --set key=value (highest priority) or -f custom-values.yaml
Use dict to pass multiple named variables to include template functions
Use $ to access root context inside range loops
Always quote string values with | quote to prevent YAML type coercion