Introduction to Helm Templates
Helm Templates generate manifest files, which are YAML-formatted resource descriptions that Kubernetes can understand.
Helm benefits:
- Faster Debugging – Renders Helm charts locally without deploying them to Kubernetes, allowing you to inspect the final YAML output before applying it.
- GitOps Friendly – Enables storing fully rendered Kubernetes manifests in Git for version control and auditing.
- Troubleshooting – Assists in debugging templates before deploying to detect syntax or logic errors.
Basic Helm Template Structure
1. Describe the structure of a Helm chart
A Helm chart is a collection of files that describe a Kubernetes application. The structure of a Helm chart typically looks like this:
my-chart/
│── charts/ # Subcharts (dependencies) can be placed here
│── templates/ # Contains Helm template files
│ ├── deployment.yaml # Kubernetes Deployment manifest template
│ ├── service.yaml # Kubernetes Service manifest template
│ ├── ingress.yaml # Ingress manifest template (if needed)
│ ├── _helpers.tpl # Helper functions (e.g., common labels, annotations)
│ ├── NOTES.txt # Post-installation usage instructions
│ ├── hpa.yaml # Horizontal Pod Autoscaler template (optional)
│ ├── configmap.yaml # ConfigMap template (optional)
│ ├── secret.yaml # Secret template (optional)
│── values.yaml # Default values for the chart
│── Chart.yaml # Metadata about the chart (name, version, description)
│── README.md # Documentation for using the chart
│── .helmignore # Files to ignore when packaging the chart
2. Purpose of the templates/ Directory
The templates/ directory contains the Kubernetes resource templates that Helm will process before deploying. These files are written in YAML but use Go templating ({{ ... }}
) to inject dynamic values
3. Common Template Files
deployment.yaml
:
- Defines the Deployment resource, which manages the application pods.
- Uses Helm template syntax to allow customization of replicas, container images, environment variables, etc
- Example snippet:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: my-app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
service.yaml
:
- Defines a Service resource to expose the application internally or externally.
- Example snippet:
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
_helpers.tpl
: is a template helper file in a Helm chart.
- Contains reusable template functions like common labels, annotations, and naming conventions.
- These functions help reduce redundancy and improve maintainability by allowing you to reuse common labels, names, and other template logic across multiple YAML manifests
- Defining Common Labels:
- This defines a reusable function
"common.labels"
for setting Kubernetes labels. - The
Chart.Name
andRelease.Name
values are dynamically inserted.
- This defines a reusable function
{{- define "common.labels" -}}
app-common: {{ .Chart.Name }}
release-common: {{ .Release.Name }}
chart-common: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
{{- end }}
- Using
_helpers.tpl
indeployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
{{- include "common.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
template:
metadata:
labels:
{{- include "common.labels" . | nindent 8 }}
- Output Example after using the template:
# Source: app-demo/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: release-name-app-demo
labels:
helm.sh/chart: app-demo-0.1.0
app.kubernetes.io/name: app-demo
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
app-common: app-demo
release-common: release-name
chart-common: app-demo-0.1.0
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: app-demo
app.kubernetes.io/instance: release-name
template:
metadata:
labels:
helm.sh/chart: app-demo-0.1.0
app.kubernetes.io/name: app-demo
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
app-common: app-demo
release-common: release-name
chart-common: app-demo-0.1.0
spec:
serviceAccountName: release-name-app-demo
securityContext:
NOTES.txt
:
- Provides post-installation instructions to the user after running commands, which means it will show the content of
NOTES.txt
.
helm install app-demo app-demo/
helm status app-demo
- Example:
### helm install app-demo app-demo/
NAME: app-demo
LAST DEPLOYED: Tue Feb 4 23:56:48 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands: OK BRO ( this is edit for testing xD)
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=app-demo,app.kubernetes.io/instance=app-demo" -o jsonpath="{.items[0].metadata.name}")
..... and for the rest of file notes.txt ....
Go Templating in Helm: Basic Syntax, Variables, and Common Functions
Helm templates use Go templating language, which provides powerful features like variables, functions, loops, and conditionals to dynamically generate Kubernetes manifests.
1. Basic Syntax
Helm templates use {{ ... }}
to enclose Go template expressions.
A template file can include:
- Variables: Store and reuse values.
- Conditionals:
if
,else
,with
. - Loops:
range
for iterating over lists or maps. - Functions: Modify values dynamically.
Example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount | default 1 }}
.Release.Name
= Helm release name..Values.replicaCount | default 1
= Use.Values.replicaCount
, or default to1
.
2. Variables
Defining Variables
- Use
{{ $var := value }}
to define a variable.
{{- $appName := .Chart.Name }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $appName }}
$appName
stores.Chart.Name
and is reused later.
Built-in Variables
Variable | Description |
---|---|
.Release.Name |
The name of the Helm release |
.Chart.Name |
The name of the chart |
.Chart.Version |
The version of the chart |
.Values |
Access values from values.yaml |
.Files |
Access files inside chart/templates |
.Capabilities |
Kubernetes API version detection |
3. Common Functions
Helm provides several built-in functions.
(A) default
– Provide a Default Value
replicas: {{ .Values.replicaCount | default 2 }}
- If
replicaCount
is not set, it defaults to2
.
(B) include
– Reuse Other Templates
The include
function allows you to reuse defined templates from _helpers.tpl
.
- Example: Defining a helper in
_helpers.tpl
{{- define "common.labels" -}}
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
{{- end }}
- Using it in
deployment.yaml
metadata:
labels:
{{- include "common.labels" . | nindent 4 }}
include "common.labels" .
injects the labels from_helpers.tpl
.nindent 4
ensures proper YAML indentation.
(C) tpl
– Render Strings as Templates
The tpl
function processes dynamic content inside values.
- Example in
values.yaml
customMessage: "Welcome to {{ .Release.Name }}!"
- Using it in
configmap.yaml
data:
message: {{ tpl .Values.customMessage . }}
tpl
evaluates{{ .Release.Name }}
inside the string fromvalues.yaml
.
(D) toYaml
– Convert Objects to YAML
Use toYaml
when dealing with structured values.
- Example in
values.yaml
envVars:
- name: ENV1
value: "value1"
- name: ENV2
value: "value2"
- Using it in
deployment.yaml
env:
{{- toYaml .Values.envVars | nindent 4 }}
toYaml
converts the YAML list into properly formatted output.
(E) quote
and nquote
– Handle Strings Properly
- quote
wraps values in double quotes.
- nquote
removes surrounding quotes.
Example:
env:
API_URL: {{ .Values.apiUrl | quote }}
For:
apiUrl: http://example.com
It renders:
env:
API_URL: "http://example.com"
(F) required
– Ensure a Value is Set
If a value is missing, required
returns an error.
Example:
image: {{ required "An image repository is required!" .Values.image.repository }}
If .Values.image.repository
is missing, Helm will fail with:
Error: template: missing required value: An image repository is required!
4. Conditionals (if
, else
, with
)
(A) if
Statement
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}
{{- end }}
- If
ingress.enabled
istrue
, this block is included.
(B) else
Statement
replicas:
{{- if .Values.replicaCount }}
{{ .Values.replicaCount }}
{{- else }}
1
{{- end }}
- If
replicaCount
exists, it uses the value; otherwise, it defaults to1
.
(C) with
Statement (Scoped Variables)
with
changes the scope to a nested value.
{{- with .Values.service }}
apiVersion: v1
kind: Service
metadata:
name: {{ .name }}
ports:
- port: {{ .port }}
{{- end }}
Instead of writing .Values.service.name
and .Values.service.port
, we scope it under with
.
5. Loops (range
)
(A) Loop Over a List
containers:
{{- range .Values.containers }}
- name: {{ .name }}
image: {{ .image }}
{{- end }}
For:
containers:
- name: "app1"
image: "nginx"
- name: "app2"
image: "redis"
It renders:
containers:
- name: "app1"
image: "nginx"
- name: "app2"
image: "redis"
(B) Loop Over a Dictionary
config:
{{- range $key, $value := .Values.config }}
{{ $key }}: {{ $value }}
{{- end }}
For:
config:
LOG_LEVEL: "debug"
ENV: "production"
It renders:
config:
LOG_LEVEL: debug
ENV: production
- In this case
toYaml
works well also xD
Useful commands for developing templates
- Show the template of the specific file only:
helm template . --show-only templates/service.yaml
- Check templates/manifest error:
helm lint
Helm template repo:
- https://github.com/BlackMetalz/k8s-manifest/tree/main/helm-templates/templates
- Include some examples with configMap, namespace, secret...
- Examples can be found in values.yaml
Use case with helm templates
Well, we can do a lot of things with helm templates.
Use scenarios: Become a general template of all helm applications - Each values file is an application in an environment - Each values file is a application in an environment
k8s-manifest/
├── helm-templates/
│ ├── ... (other files)
├── helm-services/
│ ├── group1/
│ │ ├── service1.yaml
│ │ ├── service2.yaml
│ ├── group2/
│ ├── notification.yaml
│ ├── api.yaml
- You get the idea, right? Next is the Argocd application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-name-here
namespace: argocd
spec:
project: development_or_production
source:
repoURL: https://github.com/BlackMetalz/k8s-manifest
targetRevision: main
path: helm-templates/
plugin:
name: argocd-vault-plugin-helm
env:
- name: HELM_ARGS
value: -f /helm-services/group1/service1.yaml
destination:
server: which_server_here
namespace: namespace_here
syncPolicy:
syncOptions:
- CreateNamespace=true
- With this, we can do a lot of custom modifications to the manifest without manually changing too much. That is why i think helm templates rock and are easier to begin with than jsonnet xD
Ref
- https://helm.sh/docs/chart_template_guide/
- https://www.xenonstack.com/blog/helm-kubernetes/