Skip to content

Helm Charts for Kubernetes — Templates, Values, Releases, and Dependency Management

DodaTech Updated 2026-06-22 8 min read

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

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

  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 for every value used in templates.

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

  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 and leave the chart unchanged.

  4. Ignoring helm lint: Charts with syntax errors or missing required fields 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.

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

  1. What is the difference between helm install and helm upgrade? Answer: helm install creates the first release of a chart. helm upgrade updates an existing release to a new chart version or configuration, creating a new revision.

  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, following the cascade: --set > --values > values.yaml.

  3. What is the purpose of _helpers.tpl in a chart? Answer: _helpers.tpl defines reusable named templates for consistent naming, labels, and common patterns across all manifests in the chart.

  4. How does helm rollback work internally? Answer: Helm stores each release revision in Secrets within the cluster. helm rollback reapplies 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