Lesson 03 Templating Fundamentals
Theme:

Lab Guide: Templating Fundamentals

Lesson 03 — 3 exercises, ~45 minutes

Prerequisites

Exercise 1: Values and Pipelines

Create a chart that uses .Values, .Release, and .Chart objects with pipeline functions.

Step 1 — Create a fresh chart

helm create templating-lab
cd templating-lab

Step 2 — Replace values.yaml

Overwrite values.yaml with the following content:

# values.yaml
appName: my-web-app
replicaCount: 2
image:
  repository: nginx
  tag: "1.25"
environment: staging
logLevel: debug

Step 3 — Create a ConfigMap template

Create the file templates/configmap.yaml (delete any existing template files first for a clean workspace):

rm templates/*.yaml templates/*.txt 2>/dev/null
cat > templates/configmap.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
  namespace: {{ .Release.Namespace }}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    app.kubernetes.io/managed-by: {{ .Release.Service }}
data:
  APP_NAME: {{ .Values.appName | upper | quote }}
  ENVIRONMENT: {{ .Values.environment | default "production" | quote }}
  LOG_LEVEL: {{ .Values.logLevel | default "info" | upper | quote }}
  REPLICA_INFO: {{ printf "Running %d replicas" (.Values.replicaCount | int) | quote }}
EOF

Step 4 — Render the template

helm template my-release . --namespace demo

✓ Expected Output:

---
# Source: templating-lab/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-release-config
  namespace: demo
  labels:
    app.kubernetes.io/name: templating-lab
    app.kubernetes.io/version: "0.1.0"
    app.kubernetes.io/managed-by: Helm
data:
  APP_NAME: "MY-WEB-APP"
  ENVIRONMENT: "staging"
  LOG_LEVEL: "DEBUG"
  REPLICA_INFO: "Running 2 replicas"

Step 5 — Test with overrides

Verify that --set overrides work and the default function activates when a value is missing:

helm template my-release . --set environment="" --set logLevel=""

✓ Expected Output (data section):

data:
  APP_NAME: "MY-WEB-APP"
  ENVIRONMENT: "production"
  LOG_LEVEL: "INFO"
  REPLICA_INFO: "Running 2 replicas"

Key Takeaway

Pipelines read left-to-right: .Values.logLevel | default "info" | upper first falls back to "info" if empty, then uppercases the result.

Exercise 2: Control Structures

Add conditional resources and iteration to your chart.

Step 1 — Update values.yaml

Add the following to the bottom of values.yaml:

ports:
  - name: http
    containerPort: 80
    protocol: TCP
  - name: metrics
    containerPort: 9090
    protocol: TCP

env:
  - name: DATABASE_URL
    value: postgres://db:5432/app
  - name: CACHE_TTL
    value: "300"

resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

ingress:
  enabled: true
  host: myapp.example.com

Step 2 — Create a Deployment template with range

cat > templates/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-app
spec:
  replicas: {{ .Values.replicaCount | default 1 }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
          {{- range .Values.ports }}
            - name: {{ .name }}
              containerPort: {{ .containerPort }}
              protocol: {{ .protocol | default "TCP" }}
          {{- end }}
          {{- if .Values.env }}
          env:
          {{- range .Values.env }}
            - name: {{ .name }}
              value: {{ .value | quote }}
          {{- end }}
          {{- end }}
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
EOF

Step 3 — Create a conditional Ingress template

cat > templates/ingress.yaml <<'EOF'
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}-ingress
spec:
  rules:
    - host: {{ .Values.ingress.host | default (printf "%s.local" .Release.Name) }}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: {{ .Release.Name }}-svc
                port:
                  number: 80
{{- end }}
EOF

Step 4 — Render and verify

helm template my-release .

✓ Expected: Deployment ports section

          ports:
            - name: http
              containerPort: 80
              protocol: TCP
            - name: metrics
              containerPort: 9090
              protocol: TCP

✓ Expected: Deployment env section

          env:
            - name: DATABASE_URL
              value: "postgres://db:5432/app"
            - name: CACHE_TTL
              value: "300"

✓ Expected: Resources section

          resources:
            limits:
              cpu: 500m
              memory: 256Mi
            requests:
              cpu: 100m
              memory: 128Mi

Step 5 — Disable ingress and verify it disappears

helm template my-release . --set ingress.enabled=false | grep -c "kind: Ingress"

✓ Expected Output:

0

Exercise 3: Whitespace Control

Practice the difference between trimmed and untrimmed template actions.

Step 1 — Create a whitespace test template

cat > templates/whitespace-test.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-ws-test
  labels:
    {{- if .Values.appName }}
    app: {{ .Values.appName }}
    {{- end }}
    {{- with .Values.environment }}
    env: {{ . }}
    {{- end }}
    chart: {{ .Chart.Name }}
data:
  {{- range $key, $val := .Values.env }}
  {{ $key }}_KEY: {{ $val.name | quote }}
  {{- end }}
EOF

Step 2 — Render and inspect whitespace

helm template my-release . -s templates/whitespace-test.yaml

✓ Expected Output:

---
# Source: templating-lab/templates/whitespace-test.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-release-ws-test
  labels:
    app: my-web-app
    env: staging
    chart: templating-lab
data:
  0_KEY: "DATABASE_URL"
  1_KEY: "CACHE_TTL"

Step 3 — Experiment: remove trim markers

Edit the template and remove all - trim markers from {{-. Re-render and observe the blank lines that appear in the output. Then restore the markers.

# See the difference
sed 's/{{-/{{/g' templates/whitespace-test.yaml > /tmp/no-trim.yaml
cp /tmp/no-trim.yaml templates/whitespace-test-notrim.yaml
helm template my-release . -s templates/whitespace-test-notrim.yaml
rm templates/whitespace-test-notrim.yaml

Observe

Without trim markers you will see extra blank lines in the labels and data sections. These blank lines are harmless for ConfigMaps but can break stricter YAML parsers and make output hard to read.

Cleanup

cd ..
rm -rf templating-lab

Lab Complete

You have practiced value interpolation with pipelines, conditional resources with if, scope changes with with, iteration with range, the toYaml | nindent pattern, and whitespace control. Continue to the Homework for additional challenges.