Helm Charts for Kubernetes â Templates, Values, Releases, and Dependency Management
In this tutorial, you'll learn about Helm Charts for Kubernetes. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Helm is a package manager for Kubernetes that bundles related Kubernetes manifests into reusable charts with configurable parameters, release management, and dependency resolution.
What You'll Learn
Why It Matters
Writing raw Kubernetes YAML for every application becomes unmanageable as you scale â environment-specific values are duplicated across files, common patterns are copy-pasted, and rolling back a failed deployment requires manual intervention. Helm solves this by templating YAML, separating configuration from manifests, and providing one-command installs, upgrades, and rollbacks.
Real-World Use
DodaTech packages Durga Antivirus Pro's backend stack as a Helm chart with subcharts for the API, worker, and database Migration components. A single helm upgrade command deploys any version to any environment â values files for dev, staging, and prod override the defaults without modifying the chart itself.
flowchart TD
A[Chart.yaml] --> D[Helm Package]
B[values.yaml] --> D
C[templates/] --> D
D --> E[Chart Archive .tgz]
E --> F[helm install]
E --> G[helm upgrade]
E --> H[helm rollback]
F --> I[Release v1]
G --> J[Release v2]
H --> I
style D fill:#0F1689,color:#fff
Prerequisites: Working Kubernetes knowledge â Pods, Deployments, Services, and ConfigMaps. A running cluster (minikube or kind) and Helm CLI installed.
Chart Structure
A Helm chart is a directory with a standard layout:
myapp/
Chart.yaml # Metadata: name, version, dependencies
values.yaml # Default configuration values
values.schema.json # JSON Schema for values validation
charts/ # Dependent charts (subcharts)
crds/ # Custom Resource Definitions
templates/
_helpers.tpl # Named template helpers
deployment.yaml # Deployment manifest template
service.yaml # Service manifest template
configmap.yaml # ConfigMap manifest template
ingress.yaml # Ingress manifest template
NOTES.txt # Post-install instructions
# Chart.yaml
apiVersion: v2
name: myapp
description: A production-ready web application
type: application
version: 1.2.0
appVersion: "2.5.0"
kubeVersion: ">=1.25.0"
keywords:
- web
- api
- backend
home: https://example.com
maintainers:
- name: DevOps Team
email: devops@example.com
dependencies:
- name: postgresql
version: "~12.1.0"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
Expected behavior: The chart can be installed directly or packaged into a .tgz archive. Dependencies are fetched automatically during helm dependency update.
Templates and Values
Templates use Go's text/template syntax with Sprig functions to generate Kubernetes YAML.
# values.yaml â defaults that users override
replicaCount: 3
image:
repository: myapp/api
tag: ""
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: false
host: api.example.com
tls: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
env:
NODE_ENV: production
LOG_LEVEL: info
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
protocol: TCP
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
Expected behavior: The template references .Values.replicaCount, .Values.image.Repository, .Values.env, and other values. When rendered with a values file, these placeholders produce valid Kubernetes YAML.
# templates/_helpers.tpl
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- 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 }}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.name" . }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
Expected behavior: _helpers.tpl defines reusable named templates for consistent naming and labels across all manifests. Every generated resource uses these helpers, ensuring labels follow the Kubernetes recommended standards.
Managing Releases
# Install a release
helm install myapp ./myapp-chart \
--values values-prod.yaml \
--namespace production
# Expected output:
# NAME: myapp
# LAST DEPLOYED: Mon Jun 22 2026
# NAMESPACE: production
# STATUS: deployed
#
# NOTES:
# 1. Get the application URL by running:
# kubectl get svc -n production myapp
# List releases
helm list -n production
# Expected output:
# NAME NAMESPACE REVISION UPDATED STATUS
# myapp production 1 2026-06-22 10:00:00 deployed
# Upgrade to a new version
helm upgrade myapp ./myapp-chart \
--set image.tag=2.6.0 \
--reuse-values
# Expected output:
# Release "myapp" has been upgraded. Happy Helming!
# NAME: myapp
# LAST DEPLOYED: Mon Jun 22 2026
# NAMESPACE: production
# STATUS: deployed
# REVISION: 2
# Roll back to revision 1
helm rollback myapp 1
# Expected output:
# Rollback was a success! Happy Helming!
Expected behavior: helm install creates the first release. helm upgrade creates revision 2 while keeping revision 1 for rollback. helm rollback reverts to any previous revision instantly.
Dependencies
Charts can depend on other charts, enabling composition of complex stacks.
# Chart.yaml with dependencies
dependencies:
- name: postgresql
version: "~12.1.0"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
alias: db
- name: redis
version: "~18.0.0"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# Update dependencies (downloads charts into charts/)
helm dependency update ./myapp-chart
# Expected output:
# Hang tight while we grab the latest from your chart repositories...
# ...Successfully got an update from the "bitnami" chart repository
# Update Complete. | Happy Helming!
# Build dependencies (also updates lock file)
helm dependency build ./myapp-chart
Expected behavior: helm dependency update downloads the specified versions of PostgreSQL and redis into the charts/ directory. The condition field allows disabling subcharts via values.yaml when not needed.
# values.yaml â override subchart values
postgresql:
enabled: true
auth:
database: myapp
username: myapp
password: changeme
primary:
resources:
requests:
memory: 256Mi
cpu: 250m
redis:
enabled: true
architecture: standalone
auth:
enabled: true
password: redispass
Testing Charts
Helm has a built-in test framework that runs pods to verify a release is working.
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-test-connection"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
# Run tests
helm test myapp -n production
# Expected output:
# Pod myapp-test-connection pending
# Pod myapp-test-connection succeeded
# NAME: myapp
# LAST DEPLOYED: Mon Jun 22 2026
# NAMESPACE: production
# STATUS: deployed
# TEST SUITE: myapp-test-connection
# Last Started: Mon Jun 22 2026
# Last Completed: Mon Jun 22 2026
# Phase: Succeeded
Expected behavior: Helm creates a test Pod that verifies the service responds. Test pods are cleaned up after completion. Tests can be run any time after installation.
Common Errors
Missing
.Valuesreferences in templates: If a template references a value that doesn't exist invalues.yaml, the chart renders empty strings or fails. Always define defaults for every value used in templates.Not pinning dependency versions: Using
version: "*"for dependencies pulls the latest version on every build, which can introduce breaking changes unexpectedly. Use semver ranges like~12.1.0.Overwriting
values.yamlinstead of using override files: Editing the chart'svalues.yamldirectly makes upgrades impossible. Provide custom values files per environment and leave the chart unchanged.Ignoring
helm lint: Charts with syntax errors or missing required fields fail at install time. Runhelm lintbefore every commit to catch issues early.Not using
--atomicfor critical upgrades: Without--atomic, a failed upgrade leaves the release in a broken state.--atomicrolls back automatically on failure.Storing secrets in values.yaml: Values files are often committed to Git. Use a SealedSecret controller or Git-encrypted files with SOPS instead.
Practice Questions
What is the difference between
helm installandhelm upgrade? Answer:helm installcreates the first release of a chart.helm upgradeupdates an existing release to a new chart version or configuration, creating a new revision.How do values files override chart defaults? Answer: When you pass
--values prod.yaml, Helm merges those values with the chart'svalues.yaml. User-provided values take precedence, following the cascade:--set>--values>values.yaml.What is the purpose of
_helpers.tplin a chart? Answer:_helpers.tpldefines reusable named templates for consistent naming, labels, and common patterns across all manifests in the chart.How does
helm rollbackwork internally? Answer: Helm stores each release revision in Secrets within the cluster.helm rollbackreapplies the manifests from the specified revision, returning the cluster to that previous state.
Challenge
Create a Helm chart for a microservice with the following requirements: Deployment with configurable replicas, resource limits, and probes; Service of configurable type; Ingress with optional TLS; ConfigMap for environment variables; dependency on Bitnami PostgreSQL subchart; and a test Pod that verifies the health endpoint. Package the chart, install it in a namespace, upgrade the image tag, roll back, and run tests.
Mini Project
Build a complete Helm chart for a full-stack application. Include a Deployment, Service, Ingress, ConfigMap, and Secret template. Define values for dev, staging, and production environments. Add a PostgreSQL subchart dependency with configurable resources. Implement _helpers.tpl for consistent labels. Use helm lint, install the chart in a kind cluster, perform a rolling upgrade, roll back a failed release, and run helm test to verify connectivity.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro