Helm Charts â Package Management for Kubernetes Guide
In this tutorial, you'll learn about Helm Charts. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Helm is the package manager for Kubernetes that bundles related manifests into reusable charts with configurable parameters, release management, dependency resolution, and one-command rollbacks.
What You'll Learn
Why It Matters
Raw Kubernetes YAML becomes unmanageable as you scale â environment-specific values are duplicated, common patterns are copy-pasted, and rolling back requires manual kubectl apply of previous versions. Helm solves this with templating, value separation, and release versioning. DodaTech packages all Microservices as Helm charts, enabling consistent deployments across dev, staging, and production.
Real-World Use
DodaZIP's backend stack is a single Helm chart with subcharts for the API, worker, cache, and database components. Environment-specific values files override defaults without modifying the chart. A helm upgrade --atomic command deploys any version to any environment with automatic rollback on failure.
flowchart TD
A[Chart.yaml] --> D[Helm Package]
B[values.yaml] --> D
C[templates/] --> D
D --> E[.tgz Archive]
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). A cluster (minikube or kind) and Helm CLI installed.
Chart Structure
dodazip/
Chart.yaml # Metadata, version, dependencies
values.yaml # Default configuration
values.schema.json # JSON Schema validation
charts/ # Subchart dependencies
crds/ # Custom Resource Definitions
templates/
_helpers.tpl # Named template helpers
deployment.yaml # Deployment manifest
service.yaml # Service manifest
ingress.yaml # Ingress manifest
configmap.yaml # ConfigMap manifest
hpa.yaml # Horizontal Pod Autoscaler
NOTES.txt # Post-install instructions
tests/
test-connection.yaml
# Chart.yaml
apiVersion: v2
name: dodazip
description: DodaZIP backend API service
type: application
version: 1.2.0
appVersion: "2.5.0"
kubeVersion: ">=1.25.0"
keywords:
- file-compression
- api
- backend
home: https://dodatech.com
maintainers:
- name: Platform Team
email: platform@dodatech.com
dependencies:
- name: postgresql
version: "~15.0.0"
repository: oci://registry-1.docker.io/bitnamicharts
condition: postgresql.enabled
- name: redis
version: "~19.0.0"
repository: oci://registry-1.docker.io/bitnamicharts
condition: redis.enabled
Values and Templates
# values.yaml
replicaCount: 3
image:
repository: registry.dodatech.com/dodazip
tag: ""
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
host: api.dodatech.com
tls:
enabled: true
secretName: dodatech-tls
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
env:
NODE_ENV: production
LOG_LEVEL: info
API_URL: https://api.dodatech.com
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "dodazip.fullname" . }}
labels:
{{- include "dodazip.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "dodazip.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "dodazip.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
# templates/_helpers.tpl
{{- define "dodazip.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "dodazip.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 "dodazip.labels" -}}
helm.sh/chart: {{ include "dodazip.name" . }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "dodazip.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "dodazip.selectorLabels" -}}
app.kubernetes.io/name: {{ include "dodazip.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
Release Management
# Install a release
helm install dodazip ./dodazip \
--namespace production \
--values values-prod.yaml \
--atomic \
--timeout 5m
# Expected output:
# NAME: dodazip
# LAST DEPLOYED: Wed Jun 24 2026
# NAMESPACE: production
# STATUS: deployed
# REVISION: 1
# Upgrade to new version
helm upgrade dodazip ./dodazip \
--set image.tag=2.6.0 \
--reuse-values
# Expected output:
# Release "dodazip" has been upgraded. Happy Helming!
# REVISION: 2
# Roll back to revision 1
helm rollback dodazip 1
# Expected output:
# Rollback was a success! Happy Helming!
# List releases
helm list -n production
# Expected output:
# NAME NAMESPACE REVISION UPDATED STATUS
# dodazip production 3 2026-06-24 10:00:00 deployed
Lifecycle Hooks
# templates/pre-upgrade-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "dodazip.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: ["/app/bin/run-migrations"]
env:
- name: DATABASE_URL
value: {{ .Values.database.url }}
Testing Charts
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "dodazip.fullname" . }}-test-connection"
labels:
{{- include "dodazip.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "dodazip.fullname" . }}:{{ .Values.service.port }}/health']
restartPolicy: Never
# Run tests
helm test dodazip -n production
# Expected output:
# Pod dodazip-test-connection pending
# Pod dodazip-test-connection succeeded
# NAME: dodazip
# STATUS: deployed
# TEST SUITE: dodazip-test-connection
# Last Completed: 2026-06-24 10:00:00
# Phase: Succeeded
Common Configuration Mistakes
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.Not pinning dependency versions: Using
version: "*"pulls the latest version on every build. Use semver ranges like~15.0.0for controlled updates.Overwriting
values.yamlinstead of using override files: Editing the chart'svalues.yamldirectly makes upgrades impossible. Provide custom values files per environment.Ignoring
helm lint: Syntax errors 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.
Practice Questions
What is the difference between
helm installandhelm upgrade? Answer:helm installcreates the first release.helm upgradeupdates an existing release, creating a new revision while keeping previous ones for rollback.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.What is the purpose of
_helpers.tpl? Answer: It defines reusable named templates for consistent naming and labels across all manifests, following Kubernetes recommended label standards.How do hooks differ from regular templates? Answer: Hooks are annotated templates (
helm.sh/hook: pre-upgrade) that run at specific lifecycle events. They are not part of the standard release and use separate resource management.
Challenge
Create a complete Helm chart for a microservice: Deployment with configurable replicas, resource limits, probes, and environment variables; Service and Ingress with TLS; ConfigMap for non-sensitive configuration; HorizontalPodAutoscaler; pre-upgrade hook for database migrations; PostgreSQL and Redis subchart dependencies; environment-specific values files for dev, staging, and production; and test Pod validating the health endpoint.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro