Skip to main content

Kubernetes Helm Charts Explained: Package, Configure & Deploy Applications

Calculating read time…

Think of Helm like the App Store on your phone 📱. When you want Instagram, you don't write the app yourself — you just search, tap install, and it's done. Helm does exactly that for Kubernetes apps. Instead of writing dozens of messy YAML files by hand, you install a ready-made package called a Chart with one command. 




What is Helm?

Helm is the official package manager for Kubernetes. It was adopted by the Cloud Native Computing Foundation (CNCF) in 2018 and is used by millions of engineers worldwide.

Without Helm, deploying a single app (like a database) to Kubernetes means writing and managing many YAML files separately — one for Deployment, one for Service, one for ConfigMap, one for Secret, and so on. One small typo can cause a production outage.

💡 Think of it like: Building IKEA furniture. Without Helm, someone threw all the pieces on the floor with no instructions. With Helm, you get a neat instruction booklet — and the whole wardrobe assembles in minutes!

  • Chart → The package (like an app installer)
  • Release → One installed copy of a chart running in your cluster
  • Repository → A store where charts are published and shared
  • Values → The settings you tweak to customise the chart for your needs
  • Revision → Every upgrade or rollback creates a new revision number

Why Helm? The Problem It Solves

Imagine you need to deploy your AI weather model to three environments — dev, staging, and production. Without Helm you need three near-identical sets of YAML files, each with slightly different values like replica count, image tag, and resource limits.

That means 30+ files to maintain. Change the app port? You update 30 files. Forget one? Production breaks.

With Helm you have one chart and three tiny values files. Change the port in one place and all three environments stay in sync.

Without Helm:                 With Helm:
deployment-dev.yaml           chart/
deployment-staging.yaml         templates/
deployment-prod.yaml              deployment.yaml   ← one template
service-dev.yaml                  service.yaml      ← reused
service-staging.yaml              configmap.yaml    ← everywhere
service-prod.yaml           values-dev.yaml         ← small override
configmap-dev.yaml          values-staging.yaml     ← small override
configmap-staging.yaml      values-prod.yaml        ← small override
configmap-prod.yaml
(30+ files, easy to break!)   (clean, reusable, maintainable!)

See the difference? Helm turns chaos into order. 🎯

Update — Helm 4 is Here!

Helm 4 was released at KubeCon North America in November 2025. It is the biggest update in six years and brings three major improvements every MLOps engineer needs to know about.

  • WebAssembly (Wasm) Plugins → Plugins now run in a secure sandbox. No more risk of a rogue plugin taking over your cluster.
  • Server-Side Apply (SSA) → Helm 4 uses the Kubernetes API server to apply changes. This prevents conflicts when multiple tools (like ArgoCD + Helm) manage the same resources.
  • OCI Registry as the new default → Charts are now stored and shared using the same registries as your Docker images. No more separate chart servers needed.

⚠️ Important: Helm 3 security fixes end on November 11, 2026. Plan your migration to Helm 4 before that date to stay safe and supported.

Installing Helm

Let's get Helm installed first. It takes less than two minutes!

Step 1: Install Helm on Mac

# Using Homebrew (recommended)
brew install helm

# Verify it installed correctly
helm version

Step 2: Install Helm on Linux

# Download and run the official install script
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify it installed correctly
helm version

Step 3: Install Helm on Windows

# Using Chocolatey
choco install kubernetes-helm

# Or using winget
winget install Helm.Helm

# Verify it installed correctly
helm version

Expected output after any install:

version.BuildInfo{Version:"v4.0.0", GitCommit:"abc123", GoVersion:"go1.22.0"}

You are ready to use Helm! 🎉

Your First Helm Chart — Install nginx in 60 Seconds

Let's install a real web server (nginx) on your cluster using Helm. This is the fastest way to see Helm working with your own eyes!

Step 1: Add a Chart Repository

# Add the Bitnami chart repository (one of the most popular)
helm repo add bitnami https://charts.bitnami.com/bitnami

# Always update after adding a new repo
helm repo update

Expected output:

...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

Step 2: Search for a Chart

# Search in your added repositories
helm search repo nginx

# Search the public Artifact Hub (thousands of charts)
helm search hub nginx

Expected output:

NAME            CHART VERSION   APP VERSION   DESCRIPTION
bitnami/nginx   18.2.4          1.27.3        NGINX Open Source is a web server that can be...

Step 3: Install the Chart

# Install nginx — give your release a name and choose a namespace
helm install my-nginx bitnami/nginx \
  --namespace web-apps \
  --create-namespace

Expected output:

NAME: my-nginx
LAST DEPLOYED: Thu Mar 19 2026
NAMESPACE: web-apps
STATUS: deployed
REVISION: 1
NOTES:
  Get the application URL by running these commands:
  export SERVICE_IP=$(kubectl get svc --namespace web-apps my-nginx ...)
  echo "Visit http://$SERVICE_IP to use your application"

Step 4: Verify It Is Running

# See all Helm releases in your cluster
helm list --all-namespaces

# See the Pods that Helm created
kubectl get pods -n web-apps

Helm created the Deployment, Service, ConfigMap, and all the other pieces automatically — with just one command! 🚀

Understanding Helm Chart Structure

Now let's look inside a Helm chart. Run this command to create a brand-new chart from scratch:

helm create weather-ai

Helm creates this folder structure for you:

weather-ai/
├── Chart.yaml          ← metadata: chart name, version, description
├── values.yaml         ← default settings your chart uses
├── charts/             ← dependent charts live here
├── templates/          ← Kubernetes YAML templates go here
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── serviceaccount.yaml
│   ├── _helpers.tpl    ← reusable template snippets
│   └── NOTES.txt       ← message printed after install
└── .helmignore         ← files to skip when packaging

Think of it like a recipe book 📖. The Chart.yaml is the cover page. The values.yaml is the list of ingredients with defaults. The templates/ folder is the actual cooking instructions.

The Chart.yaml File — The Identity Card

Every chart must have a Chart.yaml file. It tells Helm what the chart is called, what version it is, and what app it deploys:

# Chart.yaml

apiVersion: v2
name: weather-ai
description: AI weather prediction model for Kubernetes
type: application
version: 1.0.0          # the chart version (you change this)
appVersion: "3.1.0"     # the app version inside the chart

maintainers:
  - name: ML Team
    email: ml-team@company.com

dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
  • version → The version of the chart itself — bump this every time you change the chart
  • appVersion → The version of your actual application (like your Docker image tag)
  • dependencies → Other charts this chart needs (like PostgreSQL for a database)

The values.yaml File — Your Settings Panel

This is where all the default settings for your chart live. Think of it like a control panel with switches and dials that are set to sensible defaults:

# values.yaml (default settings)

replicaCount: 1

image:
  repository: weather-ai
  pullPolicy: IfNotPresent
  tag: "3.1.0"

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

# ML-specific settings
model:
  version: "v3.1"
  batchSize: 32
  logLevel: "info"

These are the defaults. You override them per environment without changing this file. 🎯

A Template File — Where the Magic Happens

Templates are YAML files with special placeholders wrapped in {{ }} double curly braces. Helm fills in those placeholders using your values at install time:

# templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-weather-ai
  namespace: {{ .Release.Namespace }}
  labels:
    app: {{ .Release.Name }}
    version: {{ .Chart.AppVersion }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    spec:
      containers:
        - name: weather-ai
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.port }}
          env:
            - name: MODEL_VERSION
              value: {{ .Values.model.version | quote }}
            - name: BATCH_SIZE
              value: {{ .Values.model.batchSize | quote }}

How the placeholders work:

  • {{ .Release.Name }} → The name you gave when running helm install
  • {{ .Values.replicaCount }} → Reads from values.yaml — default is 1
  • {{ .Chart.AppVersion }} → Reads from Chart.yaml
  • {{ .Values.image.tag | quote }} → Adds quotes around the value automatically

When you run helm install my-release ./weather-ai, Helm replaces all those {{ }} slots with real values and sends the final YAML to Kubernetes. Brilliant! 💡

Customising a Chart with Your Own Values

This is where Helm really shines. You can install the same chart multiple times with different settings — no duplication needed!

Method 1: Override Values Inline with --set

# Install with 3 replicas instead of the default 1
helm install weather-prod ./weather-ai \
  --set replicaCount=3 \
  --set image.tag=3.2.0 \
  --set model.logLevel=warn \
  --namespace production \
  --create-namespace

Quick and great for one-off overrides. Not ideal for many values though.

Method 2: Use a Custom Values File (Best Practice!)

Create a separate values file per environment:

# values-production.yaml

replicaCount: 5

image:
  tag: "3.2.0"

service:
  type: LoadBalancer

resources:
  limits:
    cpu: 2000m
    memory: 4Gi
  requests:
    cpu: 1000m
    memory: 2Gi

model:
  logLevel: "warn"
  batchSize: 128
# Install using the production values file
helm install weather-prod ./weather-ai \
  --values values-production.yaml \
  --namespace production \
  --create-namespace

The rule is simple: values in your file override the defaults in values.yaml. Anything you don't mention keeps its default. Clean and predictable! ✅

Essential Helm Commands — Day-to-Day Usage

Installing and Upgrading

# Install a chart
helm install <release-name> <chart> -n <namespace>

# Upgrade an existing release
helm upgrade weather-prod ./weather-ai -f values-production.yaml -n production

# Install OR upgrade in one command (most common in CI/CD)
helm upgrade --install weather-prod ./weather-ai \
  --values values-production.yaml \
  --namespace production \
  --create-namespace

Inspecting Releases

# List all releases
helm list --all-namespaces

# See detailed status of a release
helm status weather-prod -n production

# See all revisions (history) of a release
helm history weather-prod -n production

Expected output of helm history:

REVISION  UPDATED                  STATUS     CHART              DESCRIPTION
1         Thu Mar 19 09:00 2026    superseded weather-ai-1.0.0   Install complete
2         Thu Mar 19 14:00 2026    superseded weather-ai-1.0.0   Upgrade complete
3         Thu Mar 19 16:00 2026    deployed   weather-ai-1.1.0   Upgrade complete

Rolling Back — Your Safety Net

This is one of Helm's most powerful features. Something went wrong? Roll back to any previous revision instantly:

# Roll back to the previous revision
helm rollback weather-prod -n production

# Roll back to a specific revision number
helm rollback weather-prod 2 -n production

# Verify the rollback
helm history weather-prod -n production

Rollback is instant! Helm doesn't need to re-deploy anything — it just re-applies the previously stored revision. 🛡️

Previewing Before You Deploy

# Preview the final YAML that Helm would generate — nothing gets deployed
helm template weather-prod ./weather-ai -f values-production.yaml

# Check your chart for common mistakes
helm lint ./weather-ai

# Dry-run: simulate an install without actually doing it
helm upgrade --install weather-prod ./weather-ai \
  --values values-production.yaml \
  --dry-run \
  --namespace production

Always run helm lint and helm template before deploying to production. It will catch errors before they cause outages. ✅

Uninstalling a Release

# Remove everything Helm created for this release
helm uninstall weather-prod -n production

# Verify it is gone
helm list -n production

Helm Dependencies — Charts Inside Charts

Your ML model needs a database. Your database needs a message queue. Helm lets you package all of these together as dependencies.

Think of it like ordering a combo meal 🍔🍟🥤. You order one thing and all three arrive together, perfectly sized for each other.

Step 1: Declare Dependencies in Chart.yaml

# Chart.yaml

dependencies:
  - name: postgresql
    version: "12.1.5"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled     # only install if enabled in values

  - name: redis
    version: "17.3.7"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled

Step 2: Control Dependencies in values.yaml

# values.yaml

postgresql:
  enabled: true
  auth:
    postgresPassword: ""    # leave blank, pass via Secret
  primary:
    persistence:
      size: 20Gi

redis:
  enabled: false           # turn off for dev, on for prod

Step 3: Download and Install Dependencies

# Download all declared dependencies into charts/ folder
helm dependency update ./weather-ai

# Now install — Helm deploys everything together
helm install weather-stack ./weather-ai \
  --values values-production.yaml \
  --namespace ml-team \
  --create-namespace

One command deploys your ML model, its database, and its cache all together in the right order. 🎉

Helm Hooks — Running Tasks at the Right Moment

Sometimes you need to run a task before or after a deployment. For example, run database migrations before your app starts, or send a Slack notification after a successful deploy.

Think of hooks like alarm clocks ⏰ set at specific moments during the install lifecycle.

# templates/db-migration-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-db-migration
  annotations:
    "helm.sh/hook": pre-upgrade          # run before upgrade starts
    "helm.sh/hook-weight": "-5"          # run earlier than other hooks
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["python", "manage.py", "migrate"]
      restartPolicy: Never

Common hook types:

  • pre-install → Runs before any resources are created
  • post-install → Runs after all resources are created
  • pre-upgrade → Runs before an upgrade starts — perfect for DB migrations
  • post-upgrade → Runs after upgrade completes — great for smoke tests
  • pre-rollback → Runs before a rollback begins
  • post-rollback → Runs after rollback completes

OCI Registries — Way to Share Charts

Until recently, Helm charts were stored on special HTTP servers called chart repositories, OCI registries are the new standard. Your charts now live alongside your Docker images in the same registry — no separate server needed!

Think of it like keeping your app's recipe (chart) and ingredients (image) on the same shelf in the same store. Makes total sense! 🗄️

Push a Chart to an OCI Registry

# Step 1: Log in to your registry
helm registry login ghcr.io

# Step 2: Package your chart into a .tgz file
helm package ./weather-ai
# Output: weather-ai-1.0.0.tgz

# Step 3: Push to OCI registry (GitHub Container Registry in this example)
helm push weather-ai-1.0.0.tgz oci://ghcr.io/my-org/charts

Install a Chart from an OCI Registry

# Install directly from OCI registry — no repo add needed!
helm install weather-prod \
  oci://ghcr.io/my-org/charts/weather-ai \
  --version 1.0.0 \
  --namespace production

# Helm 4: Install by digest for maximum security (supply chain pinning)
helm install weather-prod \
  oci://ghcr.io/my-org/charts/weather-ai@sha256:abc123def456 \
  --namespace production

Installing by digest means even if someone tampers with the tag in the registry, your install will fail because the cryptographic hash won't match. This is the gold standard for production security ! 🔒

Helmfile — Managing Multiple Releases Declaratively

When your cluster grows and you have 10, 20, or 50 Helm releases to manage, running individual helm upgrade commands becomes painful. Helmfile is the solution.

Think of Helmfile like a grocery list 📝. Instead of going to the store and picking things up one by one, you hand the whole list to Helmfile and say "make it happen".

# helmfile.yaml

repositories:
  - name: bitnami
    url: https://charts.bitnami.com/bitnami

releases:
  - name: weather-model
    namespace: ml-team
    chart: ./weather-ai
    values:
      - values-production.yaml

  - name: postgres
    namespace: ml-team
    chart: bitnami/postgresql
    version: "12.1.5"
    set:
      - name: primary.persistence.size
        value: 50Gi

  - name: nginx-ingress
    namespace: ingress
    chart: bitnami/nginx-ingress-controller
    version: "9.x.x"
# Install or upgrade all releases in one command
helmfile apply

# Preview what would change — no actual deploy
helmfile diff

# Destroy all releases
helmfile destroy

One file. One command. Your entire cluster's desired state applied consistently. 

Testing Your Chart

Helm has a built-in test framework. You write a test Pod that verifies your app is working correctly after install. Helm runs it and reports pass or fail.

# templates/tests/test-connection.yaml

apiVersion: v1
kind: Pod
metadata:
  name: "{{ .Release.Name }}-test-connection"
  annotations:
    "helm.sh/hook": test               # marks this as a test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['{{ .Release.Name }}-weather-ai:{{ .Values.service.port }}/health']
  restartPolicy: Never
# Run all tests for a release
helm test weather-prod -n production

Expected output:

NAME: weather-prod
LAST DEPLOYED: Thu Mar 19 2026
NAMESPACE: production
STATUS: deployed
TEST SUITE: weather-prod-test-connection
Last Started: Thu Mar 19 16:45:00 2026
Last Completed: Thu Mar 19 16:45:03 2026
Phase: Succeeded

Green light! Your app passed its health check after deploying. 🟢

Real MLOps Example — Deploying a Complete ML Platform

Let's put everything together. Here is how a real ML team deploys a complete weather prediction platform using Helm.

Step 1: Create the Chart

helm create weather-ai

Step 2: Write values per environment

# values-dev.yaml
replicaCount: 1
image:
  tag: "latest"
model:
  logLevel: "debug"
  batchSize: 8
postgresql:
  enabled: true
  auth:
    postgresPassword: "dev-password"
# values-production.yaml
replicaCount: 5
image:
  tag: "3.2.0"
model:
  logLevel: "warn"
  batchSize: 128
service:
  type: LoadBalancer
postgresql:
  enabled: true
  primary:
    persistence:
      size: 100Gi

Step 3: Lint and Preview

# Check the chart for mistakes
helm lint ./weather-ai

# Preview what gets deployed to production
helm template weather-prod ./weather-ai -f values-production.yaml

Step 4: Deploy to Each Environment

# Deploy to dev
helm upgrade --install weather-dev ./weather-ai \
  --values values-dev.yaml \
  --namespace dev \
  --create-namespace

# Deploy to production
helm upgrade --install weather-prod ./weather-ai \
  --values values-production.yaml \
  --namespace production \
  --create-namespace \
  --atomic             # auto-rollback if anything fails!

The --atomic flag is critical for production. If any Pod fails to start, Helm automatically rolls back to the last working revision. Zero human intervention needed! 💎

Step 5: Package and Push to OCI Registry

# Package it
helm package ./weather-ai

# Push to your company's OCI registry
helm push weather-ai-1.0.0.tgz oci://ghcr.io/my-company/charts

# Anyone on the team can now install it from anywhere
helm install weather-prod \
  oci://ghcr.io/my-company/charts/weather-ai \
  --version 1.0.0 \
  --values values-production.yaml \
  --namespace production

Quick Summary 📝

What we learned :

  • What Helm is → The package manager for Kubernetes. Install complex apps with one command.
  • Key concepts → Chart (package), Release (installed instance), Values (your settings), Revision (every change)
  • Chart structure → Chart.yaml (identity), values.yaml (defaults), templates/ (YAML with placeholders)
  • Installing → helm install, helm upgrade --install, customise with --values or --set
  • Rollback → helm rollback reverts to any previous revision instantly
  • Dependencies → Declare sub-charts in Chart.yaml, download with helm dependency update
  • Hooks → Run Jobs before or after deploy — great for DB migrations and smoke tests
  • OCI Registries → The standard — store and share charts alongside Docker images
  • Helmfile → Manage dozens of releases declaratively from one YAML file
  • Helm 4 → WebAssembly plugins, Server-Side Apply, OCI by default

The Ultimate Cheat Sheet 📋

# ── REPOS ─────────────────────────────────────────────
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo nginx

# ── INSTALL / UPGRADE ─────────────────────────────────
helm install <name> <chart> -n <ns> --create-namespace
helm upgrade --install <name> <chart> -f values.yaml -n <ns>
helm upgrade --install <name> <chart> --atomic -n <ns>

# ── INSPECT ───────────────────────────────────────────
helm list --all-namespaces
helm status <name> -n <ns>
helm history <name> -n <ns>
helm get values <name> -n <ns>

# ── ROLLBACK ──────────────────────────────────────────
helm rollback <name> -n <ns>          # previous revision
helm rollback <name> 2 -n <ns>       # specific revision

# ── DRY RUN / PREVIEW ─────────────────────────────────
helm template <name> <chart> -f values.yaml
helm lint <chart-dir>
helm upgrade --install <name> <chart> --dry-run

# ── DEPENDENCIES ──────────────────────────────────────
helm dependency update <chart-dir>
helm dependency list <chart-dir>

# ── OCI REGISTRY ──────────────────────────────────────
helm registry login ghcr.io
helm package <chart-dir>
helm push <chart.tgz> oci://registry/org/charts
helm install <name> oci://registry/org/charts/<chart> --version x.x.x

# ── CLEANUP ───────────────────────────────────────────
helm uninstall <name> -n <ns>

Happy Helming! ⛵✨

Comments