Skip to content

Helm Charts — Package Management for Kubernetes Guide

DodaTech Updated 2026-06-24 6 min read

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
â„šī¸ Info

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

  1. Missing .Values references in templates: If a template references a value that doesn't exist in values.yaml, the chart renders empty strings or fails. Always define defaults.

  2. Not pinning dependency versions: Using version: "*" pulls the latest version on every build. Use semver ranges like ~15.0.0 for controlled updates.

  3. Overwriting values.yaml instead of using override files: Editing the chart's values.yaml directly makes upgrades impossible. Provide custom values files per environment.

  4. Ignoring helm lint: Syntax errors fail at install time. Run helm lint before every commit to catch issues early.

  5. Not using --atomic for critical upgrades: Without --atomic, a failed upgrade leaves the release in a broken state. --atomic rolls back automatically on failure.

Practice Questions

  1. What is the difference between helm install and helm upgrade? Answer: helm install creates the first release. helm upgrade updates an existing release, creating a new revision while keeping previous ones for rollback.

  2. How do values files override chart defaults? Answer: When you pass --values prod.yaml, Helm merges those values with the chart's values.yaml. User-provided values take precedence.

  3. 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.

  4. 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