Helm Package Manager for Kubernetes Applications

Deploying a real application on Kubernetes means writing and managing many YAML files — Deployments, Services, ConfigMaps, Secrets, Ingresses, ServiceAccounts, and more. As the number of environments and applications grows, copy-pasting and hand-editing these files becomes error-prone and slow. Helm solves this by packaging all the YAML for an application into a single, versioned, templatable unit called a chart.

Helm is the package manager for Kubernetes, just like apt is the package manager for Ubuntu or npm is the package manager for Node.js.

The Problem Helm Solves

Imagine deploying the same application — a web API — to three environments: development, staging, and production. Each environment needs slightly different settings: different replica counts, different resource limits, different domain names, different database URLs. Without Helm, you maintain three separate copies of 8–10 YAML files. When you update the Deployment image tag, you update it in three places and hope you do not miss one.

Without Helm                   With Helm
────────────                   ─────────
dev/deployment.yaml            chart/
dev/service.yaml                 templates/
dev/ingress.yaml                   deployment.yaml  (template)
staging/deployment.yaml            service.yaml     (template)
staging/service.yaml               ingress.yaml     (template)
staging/ingress.yaml             values.yaml        (defaults)
prod/deployment.yaml             values-dev.yaml    (overrides)
prod/service.yaml                values-staging.yaml
prod/ingress.yaml                values-prod.yaml
...                            
9 files × 3 environments       1 chart + 3 small values files
= 27 files to maintain         = easy to maintain

Helm Core Concepts

Chart

A chart is a directory of files that describes a Kubernetes application. It contains templates (YAML files with placeholders), default values, and metadata. A chart is the distributable, versioned package.

Values

Values are the variables that fill in the placeholders in the templates. The chart ships with a values.yaml file containing sensible defaults. You override specific values per environment without touching the templates.

Release

When you install a chart into a Kubernetes cluster, Helm creates a release. A release is a specific running instance of a chart with a specific set of values. You can install the same chart multiple times (each with different values) and get multiple releases.

Repository

A Helm repository is a collection of packaged charts served over HTTP. Public repositories like Artifact Hub host charts for popular open-source software — databases, monitoring stacks, ingress controllers, and more.

┌────────────────────────────────────────────────────────────┐
│                  Helm Component Relationship               │
│                                                            │
│  Repository                                                │
│  ──────────                                                │
│  chart A v1.0  ──► helm install ──► Release "my-app"       │
│  chart A v1.1                       (chart A v1.0 +        │
│  chart B v2.3                        values-prod.yaml)     │
│                                             │              │
│                                             ▼              │
│                                      Kubernetes Objects    │
│                                      (Deployment, Svc...)  │
└────────────────────────────────────────────────────────────┘

Helm Chart Structure

my-app/
├── Chart.yaml          # Chart metadata (name, version, description)
├── values.yaml         # Default values for templates
├── templates/
│   ├── deployment.yaml # Kubernetes Deployment template
│   ├── service.yaml    # Kubernetes Service template
│   ├── ingress.yaml    # Kubernetes Ingress template
│   ├── _helpers.tpl    # Reusable template snippets
│   └── NOTES.txt       # Post-install instructions shown to user
└── charts/             # Dependency charts (sub-charts)

Chart.yaml

apiVersion: v2
name: my-app
description: A web API for the my-app service
type: application
version: 1.3.0        # Chart version (you increment this)
appVersion: "2.1.0"   # Application version inside the chart

values.yaml: Default Configuration

replicaCount: 1

image:
  repository: myregistry/my-app
  tag: "2.1.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  host: "example.com"

resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

Helm Templates: Writing Parameterised YAML

Templates use the Go templating language. Double curly braces {{ }} are placeholders that Helm replaces with values at render time.

Deployment Template Example

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "my-app.selectorLabels" . | nindent 6 }}
  template:
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        resources:
          {{- toYaml .Values.resources | nindent 10 }}

When Helm renders this template with the production values file, it substitutes .Values.replicaCount with 5, .Values.image.tag with 3.0.1, and so on. The result is plain Kubernetes YAML that kubectl can apply.

Conditional Blocks

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "my-app.fullname" . }}
spec:
  rules:
  - host: {{ .Values.ingress.host }}
    ...
{{- end }}

The entire Ingress block is omitted when ingress.enabled is false. This means the development environment gets no Ingress while production does — all controlled by a single boolean in the values file.

Essential Helm Commands

Working with Repositories

# Add a repository
helm repo add bitnami https://charts.bitnami.com/bitnami

# List repositories
helm repo list

# Update cached chart index
helm repo update

# Search for charts
helm search repo nginx
helm search hub postgresql   # Search Artifact Hub

Installing and Managing Releases

# Install a chart (creates a new release named "my-nginx")
helm install my-nginx bitnami/nginx

# Install with custom values
helm install my-app ./my-app -f values-prod.yaml

# Install into a specific namespace, create it if missing
helm install my-app ./my-app \
  --namespace production \
  --create-namespace

# Preview what would be installed (dry run)
helm install my-app ./my-app --dry-run

# See the rendered YAML without installing
helm template my-app ./my-app -f values-prod.yaml

Upgrading and Rolling Back

# Upgrade a release (updates the running resources)
helm upgrade my-app ./my-app -f values-prod.yaml

# Upgrade; if it fails, automatically roll back
helm upgrade my-app ./my-app \
  --atomic \
  --timeout 5m

# See upgrade history
helm history my-app -n production

# Roll back to a previous revision
helm rollback my-app 2 -n production

Helm tracks every upgrade as a numbered revision. Rolling back is a one-command operation that restores the exact state of a previous release.

Inspecting Releases

# List all releases in a namespace
helm list -n production

# Get the values used for a release
helm get values my-app -n production

# Get the full rendered manifests for a release
helm get manifest my-app -n production

# Uninstall a release
helm uninstall my-app -n production

Values Override Hierarchy

Helm merges values from multiple sources. Later sources override earlier ones:

┌───────────────────────────────────────────────────────┐
│              Values Precedence (highest last)         │
│                                                       │
│  1. chart/values.yaml          (packaged defaults)    │
│  2. Parent chart values.yaml   (if a subchart)        │
│  3. -f values-prod.yaml        (environment file)     │
│  4. --set key=value            (command-line override)│
│                                                       │
│  Higher number wins when the same key is set twice.   │
└───────────────────────────────────────────────────────┘

A typical workflow: the chart's values.yaml holds safe defaults, each environment has its own values-{env}.yaml file, and individual deployments pass specific overrides with --set when needed.

Chart Dependencies

A chart can declare dependencies on other charts inside Chart.yaml. For example, an application chart can declare a dependency on the Bitnami PostgreSQL chart so the database gets deployed alongside the app.

# Chart.yaml
dependencies:
- name: postgresql
  version: "13.2.0"
  repository: https://charts.bitnami.com/bitnami
  condition: postgresql.enabled
# Download dependencies into the charts/ directory
helm dependency update

# Install the app + PostgreSQL together
helm install my-app ./my-app \
  --set postgresql.enabled=true \
  --set postgresql.auth.password=supersecret

Creating a Chart from Scratch

# Generate a chart scaffold
helm create my-app

# The scaffold creates a working Nginx-based chart
# Edit templates/ and values.yaml to match your app

# Lint the chart for errors
helm lint ./my-app

# Package the chart into a .tgz archive for distribution
helm package ./my-app
# Creates: my-app-1.3.0.tgz

Helm Hooks: Lifecycle Events

Helm hooks let you run Kubernetes Jobs at specific points in the chart lifecycle. Common use cases:

  • pre-install / pre-upgrade — Run database migrations before the new version of the app starts.
  • post-install / post-upgrade — Send a deployment notification or run smoke tests.
  • pre-delete — Back up data before the release is uninstalled.
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: myapp:{{ .Values.image.tag }}
        command: ["./migrate", "up"]
      restartPolicy: Never

Helm in CI/CD Pipelines

Helm integrates naturally into CI/CD workflows. A typical pipeline step upgrades a release with the new image tag as a --set override:

# In a GitHub Actions or GitLab CI step:
helm upgrade --install my-app ./chart \
  --namespace production \
  --set image.tag=$IMAGE_TAG \
  --atomic \
  --timeout 10m \
  --wait

The --atomic flag rolls back automatically if the upgrade fails. The --wait flag blocks until all Pods are ready or the timeout expires. This turns a Helm upgrade into a reliable, self-recovering deployment step.

Key Points

  • Helm packages all Kubernetes YAML for an application into a versioned, templatable chart.
  • Charts separate the template structure from environment-specific values, eliminating YAML duplication across environments.
  • A release is one installed instance of a chart. You can install the same chart multiple times with different values and get different releases.
  • Use helm upgrade --atomic in CI/CD pipelines for automatic rollback on failure.
  • Helm tracks every upgrade as a numbered revision — rolling back is a single helm rollback command.
  • Hooks let you run Jobs at lifecycle events such as pre-upgrade database migrations.
  • Public charts for databases, monitoring, and infrastructure tools are available on Artifact Hub and avoid the need to write complex YAML from scratch.

Leave a Comment