
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
A Helm Chart Templating Deep Dive starts where most tutorials stop: at the moment your chart must serve staging, production, and a client-specific namespace without forking YAML. Helm turns static Kubernetes manifests into parameterized packages. If you already ship PHP apps with GitLab CI pipelines, chart templating is the Kubernetes-side equivalent of environment-aware config. This guide walks through Go template syntax, values layering, subcharts, and the validation steps I use before anything hits a cluster.
templates/ as Go templates that read merged values.yaml files, render with helm template or helm install, and produce manifests per release, namespace, and environment without duplicating raw YAML.What is Helm chart templating and how does it structured?
Helm packages Kubernetes resources into a chart. The chart root holds metadata, default configuration, and template files. Templating is the layer that injects release name, replica counts, image tags, and secrets references at install time.
A minimal chart layout looks like this:
my-laravel-api/
├── Chart.yaml
├── values.yaml
├── values-staging.yaml
├── values-production.yaml
├── charts/ # subchart dependencies
└── templates/
├── _helpers.tpl # named templates
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
└── NOTES.txt Chart.yaml declares the chart name and version. values.yaml holds defaults. Files under templates/ are not plain YAML—they are Go templates evaluated against a merged values object plus built-in objects like .Release and .Chart.
If you are new to packaging itself, read Helm charts explained: package and deploy Kubernetes apps first. Then return here for template mechanics.
Core template objects you will use daily
Every template file receives a root context . with these common fields:
.Values— merged content fromvalues.yamland override files.Release.Name— install name, often used in resource labels.Release.Namespace— target namespace.Chart.Nameand.Chart.Version— fromChart.yaml.Capabilities— cluster version and API availability
Official reference: the Helm chart template guide documents every built-in object. Bookmark it. You will open it often.
How do you write Helm templates with Go templating functions?
Helm uses Go's text/template engine plus Sprig functions. Delimiters are {{ and }}. A typical deployment snippet wires image, replicas, and labels:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "myapp.name" . }}
template:
metadata:
labels:
app: {{ include "myapp.name" . }}
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
- containerPort: {{ .Values.service.port }} Notice three habits that prevent broken YAML:
- Put a hyphen after
{{to trim preceding whitespace:{{- include ... }} - Use
nindentto indent block output correctly - Never hard-code release-specific names; derive them from helpers
Named templates in _helpers.tpl
Duplicated label blocks are the first source of chart rot. Move them into templates/_helpers.tpl:
{{/*
Expand the name of the chart.
*/}}
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "myapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }} This pattern mirrors reusable partials in Laravel Blade components. One definition, many manifests. The trunc 63 guard matters because Kubernetes names must fit DNS label limits.
Conditionals, loops, and required values
Enable ingress only when configured:
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "myapp.fullname" . }}
spec:
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "myapp.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }} Fail fast on missing secrets with required:
env:
- name: APP_KEY
valueFrom:
secretKeyRef:
name: {{ required "secretName is required" .Values.app.secretName }}
key: app-key On production Laravel charts I maintain, missing APP_KEY references surface at render time—not after a failed pod crash loop. That saves a late-night rollback.
How does the Helm template rendering pipeline work?
Understanding render order prevents "it worked with helm template but broke on upgrade" bugs. Helm loads the chart, merges values, builds dependencies, then evaluates every file in templates/ except those starting with _ or ending in .tpl.
Files in templates/tests/ render only during helm test. Hook templates—annotated with helm.sh/hook—run at pre-install or post-upgrade phases. Use hooks sparingly for DB migrations; prefer init containers or external jobs when you need idempotency.
Teams using Flux GitOps often commit rendered manifests or let Flux run helm install from an OCI registry. Either way, the template layer stays the single source of truth.
How do you manage values.yaml and environment overrides in Helm?
Values design is where most charts succeed or fail. Keep values.yaml self-documented with sane defaults for local Minikube or kind clusters. Put environment specifics in separate files.
# values.yaml — safe local defaults
replicaCount: 1
image:
repository: ghcr.io/myorg/laravel-api
tag: ""
pullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
ingress:
enabled: false
className: nginx
hosts:
- host: api.example.local
paths:
- path: /
pathType: Prefix Override per environment:
helm upgrade --install laravel-api ./chart \
-f values.yaml \
-f values-production.yaml \
--set image.tag=2026.09.1 \
--namespace production Values merge precedence
Later files and --set flags win. Subchart values nest under the dependency name unless you use import-values or global keys.
Use global: for keys shared across subcharts—domain name, environment label, TLS issuer. Document every value in a values.schema.json file when your team grows past two engineers.
Validate JSON structure with the JSON formatter before committing schema files. Broken schema blocks helm lint in strict mode.
Secrets and ConfigMaps
Never commit real secrets into values.yaml. Patterns that work in practice:
--set-filefor small secret payloads during CI- External Secrets Operator referencing a vault
- Sealed Secrets committed as encrypted blobs
- SOPS-encrypted values files decrypted in the pipeline
On shared hosting projects I still manage with Deployer, secrets live in .env on the server. The Kubernetes equivalent is a secret reference in values, not the secret value itself. Same discipline, different runtime.
What are Helm subcharts and how do dependencies work?
Real applications rarely ship as a single Deployment. You add Redis, a queue worker, and maybe nginx. Subcharts keep each concern isolated.
Declare dependencies in Chart.yaml:
apiVersion: v2
name: laravel-stack
version: 1.4.0
appVersion: "13.0"
dependencies:
- name: redis
version: "20.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
- name: postgresql
version: "16.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled Run helm dependency update to vendor tarballs into charts/. Parent values override subchart defaults under the dependency key:
redis:
enabled: true
auth:
enabled: true
master:
persistence:
enabled: true
size: 8Gi
postgresql:
enabled: true
auth:
username: app
database: laravel When a booking platform like Adventure Third Pole Trek needs app plus worker plus Redis, one parent chart beats three copy-pasted release commands.
How do you test and validate Helm chart templates before deployment?
Rendering locally is non-negotiable. These commands belong in every CI job that publishes a chart:
# Render without touching the cluster
helm template my-release ./chart \
-f values.yaml \
-f ci/test-values.yaml \
--namespace staging > /tmp/rendered.yaml
# Lint chart structure and template syntax
helm lint ./chart --strict
# Dry-run against the API server (needs kube context)
helm upgrade --install my-release ./chart \
-f values-production.yaml \
--dry-run=server \
--namespace production Add unit tests with the helm-unittest plugin when logic branches get complex:
suite: test ingress
templates:
- ingress.yaml
tests:
- it: does not render when disabled
set:
ingress.enabled: false
asserts:
- hasDocuments:
count: 0
- it: renders host rules
set:
ingress.enabled: true
ingress.hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
asserts:
- equal:
path: spec.rules[0].host
value: api.example.com Pair chart tests with CI pipeline checks and kubeconform or kubectl apply --dry-run=server against the rendered output. Template typos are cheap to fix pre-merge. They are expensive at 2 a.m.
Common templating gotchas
Debug template expressions with helm template --debug. It prints rendered output even when YAML fails validation. For regex-heavy Sprig filters, the regex tester helps verify patterns before you embed them.
How does Helm chart templating compare to other deployment approaches?
Helm is not the only packaging model. Pick based on team skills and GitOps maturity.
| Approach | Strengths | Weaknesses | Best fit |
|---|---|---|---|
| Helm templating | Parameterised releases, subcharts, large ecosystem | Go template learning curve, drift if values diverge | Multi-env apps, vendor charts, CI-rendered manifests |
| Kustomize overlays | Patches without logic, native kubectl | Verbose at scale, limited reuse across charts | Platform teams, simple per-env diffs |
| Plain YAML + CI sed | Zero new tooling | No type checks, easy to break, poor reuse | One-off migrations only |
| Operator pattern | Reconciles live state, handles upgrades | Heavy to build and maintain | Stateful systems, CRD-driven platforms |
Many teams combine Helm with GitOps controllers. The chart templates produce manifests; Flux or Argo CD reconciles cluster state. That split matches how I treat Deployer releases on Ubuntu servers: build artifact plus automated promotion, not manual SSH edits.
For namespace isolation details, see the Kubernetes namespaces documentation. Helm releases are namespaced; cluster-scoped resources need extra template guards.
Need operational help beyond chart authoring? Linux system administration and support and maintenance cover the full deploy path—from VPS to container clusters.
Key Takeaways
- Store reusable labels and names in
_helpers.tpl; never duplicate them across manifest files. - Layer
values.yaml, environment files, and--setflags—document precedence so staging cannot accidentally inherit production secrets. - Run
helm template,helm lint --strict, and schema validation in CI before any cluster apply. - Use
required,default, andtrunc 63to fail early on missing or invalid values. - Subcharts with
conditionflags keep Redis, database, and app tiers optional without forking the parent chart. - Pair Helm with GitOps—render once, promote through namespaces, and avoid snowflake
kubectl edithotfixes.
People Also Ask
What is the difference between helm template and helm install?
helm template renders manifests locally and prints YAML to stdout. It never contacts the cluster. helm install renders the same templates, then sends resources to the Kubernetes API. Use helm template for CI diff review; use helm install or helm upgrade when you are ready to apply.
Can you use Helm charts without Go template experience?
You can install third-party charts with minimal templating knowledge. Authoring or customizing charts requires Go template basics—conditionals, ranges, and Sprig functions. The syntax pays off quickly once you manage more than one environment.
How do you upgrade Helm charts without downtime?
Use helm upgrade with rolling update strategies in the Deployment spec. Set sensible maxUnavailable and readiness probes. Run helm upgrade --dry-run=server first. For critical releases, keep the previous revision available via helm rollback.
Are Helm charts still relevant in 2026 with GitOps?
Yes. GitOps tools consume Helm as a packaging input. Charts remain the standard way to parameterise complex application stacks. GitOps adds reconciliation; it does not replace templating.
Ship repeatable Kubernetes manifests with confidence
A proper Helm Chart Templating Deep Dive changes how your team promotes software. One chart, layered values, tested templates, and a GitOps handoff beat copied YAML every time. Start from write your first Helm chart if you need a beginner path. Then apply the patterns here on your next enterprise application or custom software project.
When templating complexity outgrows your internal capacity, contact us for chart review, CI integration, or a full migration from bare manifests to maintained Helm packages.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

