USMAN’S INSIGHTS
AI ARCHITECT
  • Home
  • About
  • Thought Leadership
  • Book
Press / Contact
USMAN’S INSIGHTS
AI ARCHITECT
⌘F
HomeBook
HomeBookThe Reusable Fragment: Named Templates and Helpers
Previous Chapter
Advanced Go Templating
Next Chapter
Values Deep Dive
AI NOTICE: This is the table of contents for the SPECIFIC CHAPTER only. It is NOT the global sidebar. For all chapters, look at the main navigation.

On this page

42 sections

Progress0%
1 / 42

Muhammad Usman Akbar Entity Profile

Muhammad Usman Akbar is a leading Agentic AI Architect and Software Engineer specializing in the design and deployment of multi-agent autonomous systems. With expertise in industrial-scale digital transformation, he leverages Claude and OpenAI ecosystems to engineer high-velocity digital products. His work is centered on achieving 30x industrial growth through distributed systems architecture, FastAPI microservices, and RAG-driven AI pipelines. Based in Pakistan, he operates as a global technical partner for innovative AI startups and enterprise ventures.

USMAN’S INSIGHTS
AI ARCHITECT

Transforming businesses into autonomous AI ecosystems. Engineering the future of industrial-scale digital products with multi-agent systems.

30X Growth
AI-First
Innovation

Navigation

  • Home
  • Book
  • About
  • Contact
Let's Collaborate

Have a Project in Mind?

Let's build something extraordinary together. Transform your vision into autonomous AI reality.

Start Your Transformation

© 2026 Muhammad Usman Akbar. All rights reserved.

Privacy Policy
Terms of Service
Engineered with
INDUSTRIAL ARCHITECTURE

Named Templates and Helpers

In Lesson 1, you learned Go templating basics: variables, pipelines, conditionals, ranges. You can now write templates that branch on conditions and loop through data.

But you discovered a problem: You're repeating the same labels (app: myapp, version: 1.0, env: prod) in your Deployment, Service, and ConfigMap. That's not a templating problem—that's a code repetition problem. You need a way to define template fragments once and reuse them everywhere.

This lesson introduces named templates and the _helpers.tpl convention—the DRY principle for Helm charts. You'll define common patterns once (labels, selectors, image pull secrets, annotations) and include them wherever needed.


What You'll Learn

This lesson teaches you how to eliminate template duplication using Helm's named templates and helper conventions.

Concept GroupTopicsWhy It Matters
Template Reusedefine, end, includeStop repeating labels/selectors across manifests
Conventions_helpers.tpl, naming patternsFollow industry-standard organization
Output Controlinclude vs template, nindentPrevent broken YAML indentation
Scope Management. context, $ root referenceAccess values correctly in nested templates
Common PatternsLabels, selectors, image pull secretsApply real-world helper designs

Prerequisites:

  • Lesson 1 (Go templating: variables, pipelines, conditionals, ranges)
  • Understand YAML indentation rules
  • Familiarity with Kubernetes labels and selectors

Time Estimate: 45 minutes


1. Named Templates with define

In Lesson 1, every template you wrote was a full file that produced output. But templates can also define functions that output nothing on their own.

The define action creates a named template:

yaml
{{ define "chartname.labels" }} app: {{ .Chart.Name }} version: {{ .Chart.AppVersion }} environment: {{ .Values.environment }} {{ end }}

Key points:

  • define starts a named template block
  • chartname.labels is the template's name (convention: chart-name.component)
  • Content between define and end is stored, not immediately output
  • The block produces no output when defined

Output:

When you define a template, nothing appears. The template is registered in memory, waiting to be called.


2. The _helpers.tpl File Convention

Helm has a naming convention: Templates starting with _ (underscore) are not rendered as standalone manifests. They're utilities meant to be included elsewhere.

Create templates/_helpers.tpl:

yaml
{{- define "myapp.labels" }} app: {{ .Chart.Name }} version: {{ .Chart.AppVersion }} {{- end }} {{- define "myapp.selector" }} app: {{ .Chart.Name }} {{- end }} {{- define "myapp.imagePullSecrets" }} {{- if .Values.imagePullSecrets }} imagePullSecrets: {{- range .Values.imagePullSecrets }} - name: {{ . }} {{- end }} {{- end }} {{- end }}

Why _helpers.tpl?

  • Convention recognized by all Helm users (like _config.scss in CSS)
  • Prevents accidental rendering (underscore files are skipped)
  • Centralizes reusable fragments
  • One place to find all helpers

Output:

No files are created. _helpers.tpl is never rendered. It only provides functions for other templates to include.


3. Including Templates with include

Now you call a named template using include:

yaml
apiVersion: apps/v1 kind: Deployment metadata: labels: {{ include "myapp.labels" . | nindent 4 }} spec: selector: matchLabels: {{ include "myapp.selector" . | nindent 6 }} template: metadata: labels: {{ include "myapp.labels" . | nindent 8 }} spec: containers: - name: app image: myapp:{{ .Values.version }}

Syntax breakdown:

  • include "myapp.labels" . calls the named template, passing . (current context)
  • | nindent 4 pipes the output to nindent (indent and newline)
  • nindent N indents output by N spaces, preserving YAML structure

Output:

yaml
apiVersion: apps/v1 kind: Deployment metadata: labels: app: myapp version: 1.0 spec: selector: matchLabels: app: myapp template: metadata: labels: app: myapp version: 1.0 spec: containers: - name: app image: myapp:1.0

4. Why include Replaces the Deprecated template

Helm originally provided template for calling named templates. template is now deprecated (removed in Helm 3.13+).

Why? The problem is output handling.

Problem with template:

yaml
metadata: labels: {{ template "myapp.labels" . }}

template doesn't support pipes. You can't control indentation. Output becomes:

yaml
metadata: labels: app: myapp version: 1.0

Notice: No indentation. YAML is broken.

Solution with include:

yaml
metadata: labels: {{ include "myapp.labels" . | nindent 4 }}

include returns a string that you can pipe to nindent. Output:

yaml
metadata: labels: app: myapp version: 1.0

Properly indented. Valid YAML.

Output comparison:

template (broken indentation):

yaml
metadata: labels: app: myapp

include (correct):

yaml
metadata: labels: app: myapp

Key difference:

  • template: Renders directly, no output control
  • include: Returns string, composable with pipes

Always use include. Never use template for new code.


Checkpoint: Template Definition and Inclusion

Quick Reference:

ActionSyntaxPurpose
Define template{{ define "name" }}...{{ end }}Create reusable template fragment
Include template{{ include "name" . }}Call template and return string
Control indentation`{{ include "name" .nindent 4 }}`
Store helpers_helpers.tplConvention for non-rendered templates

Self-Check Questions:

  1. What happens when you render a template file starting with _? Answer: Nothing—underscore files are not rendered as manifests.
  2. Why does {{ template "myapp.labels" . }} break YAML indentation? Answer: template renders directly without supporting pipes, so you can't control indentation.
  3. What does the . in {{ include "myapp.labels" . }} represent? Answer: The current context being passed to the named template (root context: .Chart, .Values, etc.).
  4. Where should you define all your chart's helper templates? Answer: In templates/_helpers.tpl following Helm convention.

5. Scope Rules: What . Means Inside a Named Template

When you call a named template, you pass . as the current context. Inside the template, . refers to whatever you passed.

yaml
{{- define "myapp.labels" }} name: {{ .Chart.Name }} # Chart name from passed context env: {{ .Values.environment }} # Values from passed context {{- end }}

When you call:

Specification
{{ include "myapp.labels" . | nindent 4 }}

Inside myapp.labels, . is the root context (.Chart, .Values, etc. all work).

Scope rule: Inside a named template, . refers to whatever you passed to include/template.

Common mistake:

yaml
{{- define "myapp.labels" }} {{- range .Values.tags }} tag: {{ . }} # . is now the tag value (a string), not the root context {{- end }} {{- end }}

Inside the range, . becomes each tag string. You can't access .Chart anymore.

To access both root context and loop value:

yaml
{{- define "myapp.labels" }} {{- range .Values.tags }} tag: {{ . }} chart: {{ $.Chart.Name }} # $ always refers to root context {{- end }} {{- end }}

$ is a special variable: Always refers to root context, no matter how deep you nest.


6. Common Patterns

Pattern: Labels (Most Common)

yaml
{{- define "myapp.labels" }} app: {{ .Chart.Name }} version: {{ .Chart.AppVersion }} managed-by: {{ .Release.Service }} release: {{ .Release.Name }} {{- end }}

Used in every Kubernetes manifest.

Pattern: Selectors

yaml
{{- define "myapp.selector" }} app: {{ .Chart.Name }} {{- end }}

Used in Deployments, Services, StatefulSets.

Pattern: Image Pull Secrets (Conditional)

yaml
{{- define "myapp.imagePullSecrets" }} {{- if .Values.imagePullSecrets }} imagePullSecrets: {{- range .Values.imagePullSecrets }} - name: {{ . }} {{- end }} {{- end }} {{- end }}

Only rendered if imagePullSecrets is defined.

Pattern: Annotations (with defaults)

yaml
{{- define "myapp.annotations" }} annotations: description: {{ .Values.podAnnotations.description | default "AI Service" }} {{- if .Values.podAnnotations.custom }} custom: {{ .Values.podAnnotations.custom }} {{- end }} {{- end }}

Combines defaults with conditional fields.


7. Naming Conventions

The standard Helm convention is: chartname.component

Examples from real charts:

  • postgresql.primary.fullname
  • redis.fullname
  • nginx.ingress.className

Your chart: myapp.labels, myapp.selector, myapp.imagePullSecrets

Why this pattern?

  1. Namespacing: Prevents collisions with subchart helpers
  2. Clarity: myapp.labels immediately shows purpose
  3. Searchability: grep "myapp\." _helpers.tpl finds all your helpers
  4. Consistency: Everyone following convention means predictable code

Checkpoint: Scope and Naming Conventions

Quick Reference:

Scope ElementWhat It Refers ToExample
.Current context (what you passed to include){{ include "myapp.labels" . }} → . is root context
$Always root context, even in nested loops{{ $.Chart.Name }} inside range
.Chart, .ValuesAccessible when root context is passedOnly work if you passed . to include

Self-Check Questions:

  1. Inside a {{ range .Values.tags }} loop within a named template, what does . refer to? Answer: Each individual tag value (the current iteration item), not the root context.
  2. How do you access .Chart.Name inside a range loop in a named template? Answer: Use {{ $.Chart.Name }} where $ always refers to the root context.
  3. What naming convention should you follow for helper templates? Answer: chartname.component (e.g., myapp.labels, postgresql.fullname).
  4. Why prefix helper names with the chart name? Answer: Prevents namespace collisions with subchart helpers and improves searchability.

Common Mistakes

Before you start the exercises, avoid these frequent errors:

Mistake 1: Forgetting to Pass Context (.)

Wrong:

Specification
{{ include "myapp.labels" }}

Why it fails: The template can't access .Chart, .Values, etc. without context.

Correct:

Specification
{{ include "myapp.labels" . }}

Mistake 2: Using Wrong Indentation Level

Wrong:

Specification
metadata: labels: {{ include "myapp.labels" . | nindent 2 }}

Why it fails: Labels are already at 4-space indent (under metadata:), but nindent 2 only indents 2 spaces.

Correct:

Specification
metadata: labels: {{ include "myapp.labels" . | nindent 4 }}

Rule: Count the spaces from the left margin to where the first key should appear.


Mistake 3: Using template Instead of include

Wrong:

Specification
{{ template "myapp.labels" . }}

Why it fails: Can't pipe to nindent, breaks YAML indentation.

Correct:

Specification
{{ include "myapp.labels" . | nindent 4 }}

Mistake 4: Creating Helpers Without the Chart Name Prefix

Wrong:

Specification
{{- define "labels" }}

Why it fails: If you use a subchart that also defines labels, they collide.

Correct:

Specification
{{- define "myapp.labels" }}

Mistake 5: Losing Root Context in Nested Loops

Wrong:

Specification
{{- define "myapp.labels" }}{{- range .Values.tags }}tag: {{ . }}chart: {{ .Chart.Name }} # .Chart doesn't exist here—. is now the tag string{{- end }}{{- end }}

Correct:

Specification
{{- define "myapp.labels" }}{{- range .Values.tags }}tag: {{ . }}chart: {{ $.Chart.Name }} # $ always refers to root context{{- end }}{{- end }}

Exercises

Exercise 2.1: Create _helpers.tpl with Common Labels

Create templates/_helpers.tpl:

yaml
{{- define "aiagent.labels" }} app: {{ .Chart.Name }} version: {{ .Chart.AppVersion }} environment: {{ .Values.environment | default "development" }} managed-by: {{ .Release.Service }} {{- end }}

Verify it's not rendered:

Specification
helm template task-api ./chart

Output:

No YAML with app: aiagent appears. The template is defined but not rendered.


Exercise 2.2: Include Labels in Multiple Manifests

Create templates/deployment.yaml:

yaml
apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-app labels: {{ include "aiagent.labels" . | nindent 4 }} spec: selector: matchLabels: app: {{ .Chart.Name }} template: metadata: labels: {{ include "aiagent.labels" . | nindent 8 }} spec: containers: - name: app image: myapp:{{ .Values.version }}

Create templates/service.yaml:

yaml
apiVersion: v1 kind: Service metadata: name: {{ .Release.Name }}-service labels: {{ include "aiagent.labels" . | nindent 4 }} spec: selector: app: {{ .Chart.Name }} ports: - port: 80 targetPort: 8000

Create templates/configmap.yaml:

yaml
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-config labels: {{ include "aiagent.labels" . | nindent 4 }} data: app.env: {{ .Values.environment }}

Render:

Specification
helm template task-api ./chart

Output:

Three manifests (Deployment, Service, ConfigMap) all include the same labels from your helper.


Exercise 2.3: Create Image Pull Secrets Helper with Conditional Logic

Update templates/_helpers.tpl:

yaml
{{- define "aiagent.imagePullSecrets" }} {{- if .Values.imagePullSecrets }} imagePullSecrets: {{- range .Values.imagePullSecrets }} - name: {{ . }} {{- end }} {{- else }} # No image pull secrets configured {{- end }} {{- end }}

Update values.yaml:

yaml
environment: production version: 1.0 imagePullSecrets: - dockerhub-secret - gcr-secret

Use in templates/deployment.yaml:

yaml
spec: template: spec: {{ include "aiagent.imagePullSecrets" . | nindent 6 }} containers: - name: app

Render:

Specification
helm template task-api ./chart

Output:

Specification
spec: template: spec: image Pull Secrets: - name: dockerhub-secret - name: gcr-secret containers: - name: app

Change values.yaml to imagePullSecrets: [] and render again:

Output:

Specification
spec: template: spec: containers: - name: app

The imagePullSecrets section is omitted.


Exercise 2.4: Demonstrate Why include Works Where template Fails

Create a test file using old template syntax:

Specification
api Version: v1kind: Config Mapmetadata: labels:{{ template "aiagent.labels" . }}

Render:

Specification
helm template task-api ./chart

Output (BROKEN):

Specification
api Version: v1kind: Config Mapmetadata: labels:app: aiagentversion: 1.0environment: productionmanaged-by: Helm

YAML is invalid. Labels aren't indented under labels:.

Now use include:

Specification
api Version: v1kind: Config Mapmetadata: labels:{{ include "aiagent.labels" . | nindent 4 }}

Output (CORRECT):

Specification
api Version: v1kind: Config Mapmetadata: labels: app: aiagent version: 1.0 environment: production managed-by: Helm

Valid YAML, proper indentation.


Exercise 2.5: Refactor 3 Duplicate Sections into Single Include

You have three manifests with duplicate metadata.labels:

Before:

yaml
# deployment.yaml metadata: labels: app: myapp version: 1.0 env: prod # service.yaml metadata: labels: app: myapp version: 1.0 env: prod # configmap.yaml metadata: labels: app: myapp version: 1.0 env: prod

After:

Create helper in _helpers.tpl:

yaml
{{- define "myapp.labels" }} app: myapp version: 1.0 env: prod {{- end }}

Use in all three:

yaml
metadata: labels: {{ include "myapp.labels" . | nindent 4 }}

Benefit: Change labels once in _helpers.tpl. All three manifests automatically use the new labels.


Try With AI

Setup

You've created a chart with separate Deployment, Service, and ConfigMap. Each has duplicate labels, annotations, and image pull secret handling. Your job is to refactor common patterns into _helpers.tpl and simplify your manifests.

Files to work with:

  • templates/_helpers.tpl (create this)
  • templates/deployment.yaml
  • templates/service.yaml
  • templates/configmap.yaml
  • values.yaml

Part 1: Initial State (Current Duplications)

Show AI your current manifests:

Specification
I have three manifests with repeated labels and selectors:[paste your current deployment.yaml, service.yaml, configmap.yaml]What patterns would you identify as candidates for helpers?

Part 2: Helper Design

Based on AI's suggestions, ask:

Specification
I want to create these helpers in _helpers.tpl:1. labels (for metadata.labels in all manifests) 2. selector (for spec.selector.match Labels in Deployment/Service) 3. image Pull Secrets (conditional, only if Values.image Pull Secrets exists) For each helper, show me the define/end block and how I should call it with include and nindent.

Part 3: Validation

Ask AI to help you validate:

Specification
After I add these helpers and refactor my manifests to use include,how do I verify with `helm template` that:1. Labels appear correctly indented in all manifests? 2. Selectors are only in Deployment and Service, not Config Map? 3. Image Pull Secrets appear when configured, disappear when empty?

Part 4: Refinement

Show AI your refactored manifests:

Specification
Here's my refactored deployment.yaml using include:[paste your refactored file]Are there any places where I should add nindent, or any indentation issues?

Part 5: Final Check

Verify your work:

Specification
helm template task-api ./chart > rendered.yamlcat rendered.yaml

Compare the output:

  • Do all labels appear with correct indentation?
  • Do selectors only appear where needed?
  • Are image pull secrets included/excluded based on values.yaml?

Check your helpers are used everywhere they should be:

Specification
grep -r "include \"myapp\." templates/

Should show calls in deployment, service, configmap—not in _helpers.tpl definitions.


Reflect on Your Skill

You built a helm-chart skill in Lesson 0. Test and improve it based on what you learned.

Test Your Skill

Specification
Using my helm-chart skill, create a _helpers.tpl file with reusable label templates.Does my skill understand define/include patterns and nindent usage?

Identify Gaps

Ask yourself:

  • Did my skill create templates with proper naming (chartname.templatename)?
  • Did it use include instead of deprecated template?
  • Does it handle indentation correctly with nindent?
  • Did it demonstrate passing context with the . parameter?

Improve Your Skill

If you found gaps:

Specification
My helm-chart skill is missing [named templates / helper patterns / include syntax].Update it to include: - Naming convention: chartname.component (e.g., myapp.labels) - Using include instead of template for composability - Proper indentation with nindent function - Context passing with `.` and root access with `$` - Common helper patterns (labels, selectors, fullname)