Implementing Advanced Templating
1. Creating Recursive Templates
Recursive templates in Helm allow you to define templates that can call themselves, enabling complex nested structures like trees or hierarchical data rendering.
| Template | Description | Usage |
|---|---|---|
| {{ include "mychart.recursive" . }} | Include a recursive template | Use to render nested structures |
Example: Recursive Template for Tree Structure
{{- define "mychart.recursive" -}}
{{- if .children }}
<ul>
{{- range .children }}
<li>{{ .name }}
{{- include "mychart.recursive" . }}
</li>
{{- end }}
</ul>
{{- end }}
{{- end -}}
Note: Recursive templates must define a base case to prevent infinite loops.
2. Building Dynamic Names
| Property | Description | Example |
|---|---|---|
| nameOverride | Overrides the default name of the chart | nameOverride: my-app |
| fullnameOverride | Overrides the full name of the chart, including release name | fullnameOverride: my-release-my-app |
| name | Used to construct the full name of resources | name: nginx |
Note: The
nameOverride and fullnameOverride values are useful for customizing resource names without modifying the chart's templates.
3. Implementing Feature Flags
| Flag Name | Values | Description | Default |
|---|---|---|---|
| feature.flag1 | true, false |
Enables experimental feature 1 | false |
| feature.flag2 | true, false |
Enables experimental feature 2 | false |
Example: Conditional Template Rendering
<{{- if .Values.feature.flag1 }}>
<div>Experimental Feature 1 Enabled</div>
<{{- end }}>
Note: Feature flags can be toggled at runtime via
helm upgrade with updated values.
4. Creating Environment-Aware Templates
Environment-aware templates in Helm allow you to customize chart behavior based on the target environment (e.g., dev, staging, prod). This is typically achieved using values files and conditional logic within templates.
| Feature | Description | Usage |
|---|---|---|
| values.yaml | Defines default values for templates. Environment-specific overrides can be provided in separate files. | environment: production |
| .Values | Accesses values from the values files in templates. | {{ .Values.environment }} |
| .Release | Provides release-specific information like name and namespace. | {{ .Release.Name }} |
Example: Environment-specific ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
data:
environment: {{ .Values.environment }}
logLevel: {{ .Values.logLevel }}
Example: Conditional Deployment
{{- if eq .Values.environment "production" }}
replicas: 3
{{- else }}
replicas: 1
{{- end }}
Note: Use
.Values.environment to dynamically adjust configurations based on the environment. This allows for a single chart to be reused across multiple environments with minimal changes.
5. Using Template Variables
| Variable | Type | Description | Example |
|---|---|---|---|
| .Values | Object | Root values passed to the chart | {{ .Values.replicaCount }} |
| .Chart | Object | Chart metadata (name, version, etc.) | {{ .Chart.Name }} |
| .Release | Object | Release information (name, namespace, etc.) | {{ .Release.Name }} |
| .Capabilities | Object | Kubernetes version and features supported | {{ .Capabilities.KubeVersion.Major }} |
| .Files | Object | Access to files in the chart | {{ .Files.Get "config.yaml" }} |
| .Template | Object | Template information (name, base path) | {{ .Template.Name }} |
Note: Template variables are case-sensitive and must be accessed using the correct syntax.
6. Implementing Complex Data Structures
Example: Using Maps and Lists in Templates
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "myapp.fullname" . }}
data:
config.yaml: |
database:
host: {{ .Values.database.host }}
port: {{ .Values.database.port }}
credentials:
username: {{ .Values.database.credentials.username }}
password: {{ .Values.database.credentials.password }}
services:
- name: frontend
replicas: {{ .Values.services.frontend.replicas }}
image: {{ .Values.services.frontend.image }}
- name: backend
replicas: {{ .Values.services.backend.replicas }}
image: {{ .Values.services.backend.image }}
| Structure | Description | Usage |
|---|---|---|
| .Values.database | Map containing database configuration | Used to define connection details for databases |
| .Values.services | List of service definitions | Used to define multiple services with similar structure |
| .Values.services.frontend.replicas | Number of replicas for frontend service | Used to scale services based on resource needs |
Note: When using complex data structures, ensure that all keys are properly escaped to avoid YAML parsing errors.
Example: Nested Maps in Values
database:
host: localhost
port: 5432
credentials:
username: admin
password: secret
ssl:
enabled: true
mode: require
Example: List of Services with Variables
services:
- name: frontend
replicas: 2
image: nginx:latest
env:
- name: ENV
value: production
- name: backend
replicas: 3
image: node:18
env:
- name: NODE_ENV
value: production
7. Building Resource Templates Dynamically
| Template Function | Description | Usage Example |
|---|---|---|
| include | Includes another template file | {{ include "my-template" . }} |
| required | Ensures a value is provided or fails with error | {{ required "A valid image tag is required" .Values.image.tag }} |
| tpl | Processes a template string with values | {{ tpl "{{.Values.image.tag}}" . }} |
Example: Using include for Reusable Templates
{{- define "myapp.labels" -}}
app: {{ include "myapp.name" . }}
version: {{ .Values.image.tag }}
{{- end -}}
{{- define "myapp.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.name" . }}
spec:
replicas: {{ .Values.replicaCount }}
template:
metadata:
labels:
{{- include "myapp.labels" . | nindent 8 }}
spec:
containers:
- name: {{ include "myapp.name" . }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
{{- end -}}
Example: Using tpl for Dynamic Template Content
{{- $customEnvVars := tpl "{{.Values.customEnvVars}}" . -}}
env:
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
{{- if $customEnvVars }}
{{- $customEnvVars | nindent 2 }}
{{- end }}
Note: The
tpl function allows for dynamic template processing, which is useful when you need to inject templates or strings that contain template syntax.
8. Using Template Includes Efficiently
| Feature | Description | Usage |
|---|---|---|
| include | Template function to include content from another template file | {{ include "my-template" . }} |
| tpl | Template function to execute a template string within a template | {{ tpl "{{ .Values.name }}" . }} |
Note: Template includes are processed at render time, not during chart packaging.
Warning: Avoid deeply nested includes as they can impact performance and readability.
9. Handling Edge Cases
Example: Default Values for Missing Values
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "myapp.fullname" . }}
data:
config.yaml: |
database:
host: {{ .Values.database.host | default "localhost" }}
port: {{ .Values.database.port | default 5432 }}
| Edge Case | Description | Solution |
|---|---|---|
| Missing Values | Values not provided in values.yaml or overrides | Use | default "value" to provide fallbacks |
| Empty Strings | Values provided as empty strings | Use | default "value" or | coalesce |
| Null Values | Values explicitly set to null | Use .Values.key | default "fallback" or .Values.key | default "" |
Note: The
| default function is a powerful way to handle missing or null values in templates.
Example: Conditional Logic for Missing Values
apiVersion: v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
spec:
replicas: {{ if .Values.replicaCount }}
{{ .Values.replicaCount }}
{{ else }}
1
{{ end }}
Comparison: Default vs Coalesce
| default: Returns the first non-empty value (empty string, zero, false)| coalesce: Returns the first non-nil value (nil, empty string, zero, false)
10. Creating Maintainable Templates
| Template Type | Description | Best Practices |
|---|---|---|
| Reusability | Templates designed to be reused across multiple releases or environments | Use include and import directives to modularize templates |
| Parameterization | Templates that accept values through values.yaml |
Define default values and use {{ .Values }} to access them |
| Conditional Logic | Templates that conditionally render resources based on values | Use {{ if }} and {{ unless }} to control rendering |
Example: Reusable Template for ConfigMap
<!-- templates/configmap.yaml -->
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "myapp.fullname" . }}
data:
config.properties: |
server.port={{ .Values.server.port }}
database.url={{ .Values.database.url }}
Example: Conditional Deployment
<!-- templates/deployment.yaml -->
{{- if .Values.deployment.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "myapp.name" . }}
{{- end }}
Note: Use
{{ include }} to reference other templates and keep logic centralized.
Warning: Avoid complex logic inside templates. Keep templates simple and move complex logic to
helpers.tpl.