NexusCS

Helm

Kubernetes
Helm is the package manager for Kubernetes. This guide covers Helm v4 (latest), v3, and migration from v2. Quick reference for install, upgrade, rollback, debugging, hooks, and troubleshooting.
kubernetes
k8s
package-manager
charts
devops

Getting started

Introduction

Helm is the package manager for Kubernetes, providing templating, versioning, and dependency management for Kubernetes applications.

  • Client-only architecture (no Tiller since v3)
  • Namespace-scoped releases (Helm 3+)
  • Latest version: v4.0.0 (2026)
  • Storage: Kubernetes Secrets by default

Installation

macOS:

brew install helm

Linux (Debian/Ubuntu):

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

Snap:

sudo snap install helm --classic

Binary download:

curl -LO "https://get.helm.sh/helm-v4.0.0-linux-amd64.tar.gz"
tar -zxvf helm-v4.0.0-linux-amd64.tar.gz
sudo mv linux-amd64/helm /usr/local/bin/helm

Quick Example

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

# Update repository index
helm repo update

# Install a chart
helm install my-release bitnami/nginx

# Check release status
helm status my-release

# Upgrade release
helm upgrade my-release bitnami/nginx --set service.type=LoadBalancer

# Rollback to previous version
helm rollback my-release

# Uninstall release
helm uninstall my-release

Version History

Version Key Changes
Helm v2 Tiller server component (EOL Nov 2020)
Helm v3 Removed Tiller, Secrets storage, chart apiVersion v2
Helm v4 Plugin overhaul, server-side apply, OCI digest support

Configuration Directories

Path Purpose
~/.cache/helm Cache
~/.config/helm Configuration
~/.local/share/helm Data (Linux)

Core Commands

Install

# Basic install
helm install RELEASE_NAME CHART_NAME

# Install with custom values
helm install RELEASE_NAME CHART_NAME -f values.yaml

# Install with --set
helm install RELEASE_NAME CHART_NAME --set key=value

# Auto-generated name
helm install CHART_NAME --generate-name

# Specific namespace
helm install RELEASE_NAME CHART_NAME -n NAMESPACE --create-namespace

# Dry-run (test without installing)
helm install RELEASE_NAME CHART_NAME --dry-run --debug

# Rollback on failure (v4)
helm install RELEASE_NAME CHART_NAME --rollback-on-failure

# Wait for ready
helm install RELEASE_NAME CHART_NAME --wait --timeout 5m

# Wait for jobs
helm install RELEASE_NAME CHART_NAME --wait --wait-for-jobs

Upgrade

# Basic upgrade
helm upgrade RELEASE_NAME CHART_NAME

# Install if not exists
helm upgrade --install RELEASE_NAME CHART_NAME

# Upgrade with new values
helm upgrade RELEASE_NAME CHART_NAME -f new-values.yaml

# Reuse existing values
helm upgrade RELEASE_NAME CHART_NAME --reuse-values --set key=newvalue

# Reset then reuse values
helm upgrade RELEASE_NAME CHART_NAME --reset-then-reuse-values --set key=value

# Force resource replacement (v4)
helm upgrade RELEASE_NAME CHART_NAME --force-replace

# Cleanup on failure
helm upgrade RELEASE_NAME CHART_NAME --cleanup-on-fail

# Dry-run upgrade
helm upgrade RELEASE_NAME CHART_NAME --dry-run --debug

Rollback

# Rollback to previous revision
helm rollback RELEASE_NAME

# Rollback to specific revision
helm rollback RELEASE_NAME REVISION_NUMBER

# Rollback to previous (explicit)
helm rollback RELEASE_NAME 0

Uninstall

# Uninstall release
helm uninstall RELEASE_NAME

# Uninstall and keep history
helm uninstall RELEASE_NAME --keep-history

# Uninstall from namespace
helm uninstall RELEASE_NAME -n NAMESPACE

List

# List releases
helm list

# All namespaces
helm list --all-namespaces
helm list -A

# Include deleted
helm list --all

# Filter by status
helm list --deployed
helm list --failed
helm list --pending

Status & History

# Show release status
helm status RELEASE_NAME

# Status as YAML/JSON
helm status RELEASE_NAME -o yaml
helm status RELEASE_NAME -o json

# Show revision history
helm history RELEASE_NAME

# History as YAML
helm history RELEASE_NAME -o yaml

Get

# Get deployed manifests
helm get manifest RELEASE_NAME

# Get computed values
helm get values RELEASE_NAME

# Get all values
helm get values RELEASE_NAME --all

# Get hooks
helm get hooks RELEASE_NAME

# Get post-install notes
helm get notes RELEASE_NAME

# Get everything
helm get all RELEASE_NAME

Repository Management

Add Repository

# Add chart repository
helm repo add REPO_NAME URL

# Add with force update
helm repo add REPO_NAME URL --force-update

# Examples
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable https://charts.helm.sh/stable

Update & List

# Update all repository indexes
helm repo update

# Update specific repository
helm repo update REPO_NAME

# List repositories
helm repo list

# Remove repository
helm repo remove REPO_NAME

Search

# Search Artifact Hub
helm search hub KEYWORD

# Search local repositories
helm search repo KEYWORD

# Search with versions
helm search repo KEYWORD --versions

# Search with regex
helm search repo --regexp 'nginx.*'

Chart Structure

Directory Layout

chart-name/
  Chart.yaml          # Chart metadata
  values.yaml         # Default config
  charts/             # Dependencies
  templates/          # Templates
  templates/NOTES.txt # Post-install notes
  crds/              # CRDs
  README.md          # Documentation
  LICENSE            # License
  .helmignore        # Exclude files

Chart.yaml

Required fields:

apiVersion: v2 # v2 for Helm 3+
name: my-chart # Chart name
version: 1.0.0 # Chart version (SemVer)

Optional fields:

kubeVersion: ">=1.20.0"
description: "My application"
type: application # or 'library'
keywords:
  - web
  - app
home: https://example.com
sources:
  - https://github.com/example/repo
dependencies:
  - name: postgresql
    version: "12.1.9"
    repository: https://charts.bitnami.com/bitnami
maintainers:
  - name: John Doe
    email: john@example.com
icon: https://example.com/icon.png
appVersion: "2.0.1" # App version
deprecated: false

Chart Types

Type Description
application Default, installable chart
library Utility templates only, not installable

CRDs

Custom Resource Definitions have special handling:

  • Stored in crds/ directory
  • Cannot be templated
  • Installed before other resources
  • Never upgraded by Helm
  • Never deleted by Helm

Warning: Manage CRD upgrades manually outside Helm.

Chart Development

Create

# Scaffold new chart
helm create CHART_NAME

This creates:

  • Chart.yaml
  • values.yaml
  • templates/ directory
  • Sample Deployment, Service, Ingress

Lint

# Validate chart
helm lint CHART_PATH

# Lint with values
helm lint CHART_PATH --values values.yaml

Template

# Render templates locally
helm template RELEASE_NAME CHART_PATH

# Render with values
helm template RELEASE_NAME CHART_PATH -f values.yaml

# Show only specific template
helm template RELEASE_NAME CHART_PATH --show-only templates/deployment.yaml

# Output to directory
helm template RELEASE_NAME CHART_PATH --output-dir ./rendered

# Include CRDs
helm template RELEASE_NAME CHART_PATH --include-crds

# Debug rendering
helm template RELEASE_NAME CHART_PATH --debug

Package

# Package chart to .tgz
helm package CHART_PATH

# Package with version
helm package CHART_PATH --version 1.2.3

# Package and sign
helm package CHART_PATH --sign --key keyname --keyring ~/.gnupg/secring.gpg

Dependencies

# Download dependencies to charts/
helm dependency update CHART_PATH

# Rebuild Chart.lock
helm dependency build CHART_PATH

# List dependencies
helm dependency list CHART_PATH

Show

# Show chart metadata
helm show chart CHART_NAME

# Show default values
helm show values CHART_NAME

# Show README
helm show readme CHART_NAME

# Show all
helm show all CHART_NAME

Values & Configuration

Values Precedence

Highest to lowest:

  1. --set flags
  2. --values files (rightmost wins)
  3. Chart's default values.yaml

Using --set

# Simple value
helm install myapp ./mychart --set key=value

# Nested value
helm install myapp ./mychart --set outer.inner=value

# Multiple values
helm install myapp ./mychart --set key1=val1,key2=val2

# List values
helm install myapp ./mychart --set list={a,b,c}

# Null value
helm install myapp ./mychart --set key=null

# Empty array
helm install myapp ./mychart --set list=[]

# Escape commas
helm install myapp ./mychart --set key=value\,with\,commas

Using --values

# Single values file
helm install myapp ./mychart -f values.yaml

# Multiple files (rightmost wins)
helm install myapp ./mychart -f values-base.yaml -f values-prod.yaml

# Values from URL
helm install myapp ./mychart --values https://example.com/values.yaml

Other Value Flags

# Force string type
helm install myapp ./mychart --set-string key=123

# Read from file
helm install myapp ./mychart --set-file config=config.txt

# JSON value
helm install myapp ./mychart --set-json 'config={"key":"value"}'

Predefined Values

Available in all templates:

.Release.Name         # Release name
.Release.Namespace    # Release namespace
.Release.Service      # Always "Helm"
.Release.IsUpgrade    # true if upgrade
.Release.IsInstall    # true if install
.Chart.Name           # Chart name
.Chart.Version        # Chart version
.Chart.AppVersion     # App version
.Values               # Values object
.Files                # File accessor
.Capabilities         # Cluster capabilities

Global Values

# values.yaml
global:
  domain: example.com
  image:
    registry: docker.io

# Accessible in all charts as:
# {{ .Values.global.domain }}

Debugging

Debug Commands

# Dry-run with debug
helm install myapp ./mychart --dry-run --debug

# Template locally
helm template myapp ./mychart
helm template myapp ./mychart --debug

# Get deployed manifests
helm get manifest RELEASE_NAME

# Get computed values
helm get values RELEASE_NAME
helm get values RELEASE_NAME --all

# Verbose logging (0-10)
helm list -v 6

Template Debugging

# Render all templates
helm template myapp ./mychart --debug

# Render specific template
helm template myapp ./mychart --show-only templates/deployment.yaml

# Save rendered output
helm template myapp ./mychart --output-dir ./rendered

# Check syntax only
helm lint ./mychart

Release Debugging

# Check release status
helm status RELEASE_NAME

# Check release history
helm history RELEASE_NAME

# Get all release info
helm get all RELEASE_NAME

# Check hooks
helm get hooks RELEASE_NAME

Troubleshooting

Release Already Exists

Error: cannot re-use a name that is still in use

Diagnosis:

# Check release status
helm list -a
helm status RELEASE_NAME

# Check history
helm history RELEASE_NAME

Solution:

# Uninstall first
helm uninstall RELEASE_NAME

# Or use upgrade --install
helm upgrade --install RELEASE_NAME CHART_NAME

Template Rendering Errors

Error: Error: template: mychart/templates/deployment.yaml:10:14: executing...

Diagnosis:

# Render locally
helm template myapp ./mychart --debug

# Show specific template
helm template myapp ./mychart --show-only templates/deployment.yaml

Common fixes:

  • Undefined values: {{ .Values.key | default "value" }}
  • Required values: {{ required "msg" .Values.key }}
  • Check template syntax

Values Not Applied

Diagnosis:

# Check computed values
helm get values RELEASE_NAME

# Check all values
helm get values RELEASE_NAME --all

# Dry-run upgrade
helm upgrade RELEASE_NAME CHART_NAME --dry-run --debug

Common causes:

  • --set overrides --values
  • Typo in key name
  • Need --reuse-values for upgrades

Warning: --set always takes precedence over --values files.

Release Stuck Pending

States: pending-install, pending-upgrade, pending-rollback

Diagnosis:

# Check status
helm status RELEASE_NAME

# Check Kubernetes events
kubectl get events --sort-by='.lastTimestamp'

# Check pods
kubectl get pods -n NAMESPACE
kubectl describe pod POD_NAME -n NAMESPACE

Solution:

# Uninstall if stuck
helm uninstall RELEASE_NAME

# Check orphaned resources
kubectl get all -n NAMESPACE

Hook Failures

Diagnosis:

# Get hooks
helm get hooks RELEASE_NAME

# Check hook pods/jobs
kubectl get pods -n NAMESPACE -l 'helm.sh/hook'
kubectl logs POD_NAME -n NAMESPACE

Solution:

# Increase timeout
helm install RELEASE_NAME CHART_NAME --timeout 10m

# Clean up failed hooks
kubectl delete pod HOOK_POD_NAME -n NAMESPACE

Dependency Issues

Error: found in Chart.yaml, but missing in charts/ directory

Solution:

# Update dependencies
helm dependency update CHART_PATH

# Build dependencies
helm dependency build CHART_PATH

# List dependencies
helm dependency list CHART_PATH

Namespace Issues

Helm 3+ uses current kubectl namespace context.

Diagnosis:

# Check current namespace
kubectl config view --minify | grep namespace

# List all releases
helm list --all-namespaces

Solution:

# Specify namespace
helm install RELEASE_NAME CHART_NAME -n NAMESPACE

# Create if missing
helm install RELEASE_NAME CHART_NAME -n NAMESPACE --create-namespace

Info: Always verify namespace context with kubectl config get-contexts.

Helm Hooks

Hook Types

Hook When Executed
pre-install Before resources installed
post-install After all resources installed
pre-delete Before resources deleted
post-delete After all resources deleted
pre-upgrade Before resources upgraded
post-upgrade After all resources upgraded
pre-rollback Before rollback
post-rollback After rollback
test When helm test invoked

Hook Annotations

Declare hook:

metadata:
  annotations:
    "helm.sh/hook": post-install,post-upgrade

Hook weight (execution order, lower first):

metadata:
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "5"

Deletion policy:

metadata:
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-delete-policy": hook-succeeded,hook-failed

Deletion Policies

Policy Description
before-hook-creation Delete previous resource before new hook (default)
hook-succeeded Delete after successful execution
hook-failed Delete if hook failed

Hook Execution Order

  1. Sorted by weight (ascending)
  2. Then by kind
  3. Then by name
  4. Helm waits for "Ready" state
  5. For Job/Pod: waits for completion
  6. Hook failure causes release failure

Hook Example

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}-db-migrate"
  annotations:
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["/bin/sh", "-c", "migrate-db"]
      restartPolicy: Never

Warning: Hooks are NOT part of release and NOT deleted by helm uninstall.

Advanced Features

Atomic Operations

# Rollback on failure (v4)
helm install RELEASE_NAME CHART_NAME --rollback-on-failure

# Legacy v3 flag
helm install RELEASE_NAME CHART_NAME --atomic

Wait Flags

# Wait for resources ready
helm install RELEASE_NAME CHART_NAME --wait --timeout 10m

# Wait for jobs
helm install RELEASE_NAME CHART_NAME --wait --wait-for-jobs

Force Replace

# Force replacement (v4)
helm upgrade RELEASE_NAME CHART_NAME --force-replace

# Legacy v3 flag
helm upgrade RELEASE_NAME CHART_NAME --force

Cleanup on Fail

# Delete new resources if upgrade fails
helm upgrade RELEASE_NAME CHART_NAME --cleanup-on-fail

Reuse Values

# Reuse existing values
helm upgrade RELEASE_NAME CHART_NAME --reuse-values

# Reset to chart defaults
helm upgrade RELEASE_NAME CHART_NAME --reset-values

# Reset then reuse
helm upgrade RELEASE_NAME CHART_NAME --reset-then-reuse-values

Warning: --reuse-values preserves previous --set overrides.

Test

# Run chart tests
helm test RELEASE_NAME

# Test with logs
helm test RELEASE_NAME --logs

Plugins

# Install plugin
helm plugin install PLUGIN_URL

# List plugins
helm plugin list

# Update plugin
helm plugin update PLUGIN_NAME

# Uninstall plugin
helm plugin uninstall PLUGIN_NAME

OCI Registry

# Install from OCI registry
helm install myapp oci://registry.example.com/charts/app --version 1.0.0

# Push chart to OCI
helm push mychart-1.0.0.tgz oci://registry.example.com/charts

Best Practices

Recommended Labels

metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
    app.kubernetes.io/managed-by: {{ .Release.Service }}
    app.kubernetes.io/instance: {{ .Release.Name }}
    app.kubernetes.io/version: {{ .Chart.AppVersion }}

Chart Naming

  • Use lowercase letters, numbers, hyphens
  • Start with letter
  • Examples: my-app, postgresql, nginx-ingress

Values Organization

Good:

image:
  repository: nginx # Docker image
  tag: "1.21" # Image tag
  pullPolicy: IfNotPresent

Bad:

imageRepository: nginx
imageTag: "1.21"

Documentation

  • Include README.md with install instructions
  • Document all values.yaml options
  • Use templates/NOTES.txt for post-install guidance
  • Include prerequisites and dependencies

Template Helpers

{{- define "mychart.labels" -}}
app: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}

# Use in templates:
metadata:
  labels:
    {{- include "mychart.labels" . | nindent 4 }}

Environment Variables

Storage Backend

# Use ConfigMaps
export HELM_DRIVER=configmap

# Default: Secrets
export HELM_DRIVER=secret

# Other: memory, sql

Configuration Paths

# Override config directory
export HELM_CONFIG_HOME=/path/to/config

# Override data directory
export HELM_DATA_HOME=/path/to/data

# Override cache directory
export HELM_CACHE_HOME=/path/to/cache

# Multiple kubeconfig
export KUBECONFIG=~/.kube/config1:~/.kube/config2

Debug

# Enable debug output
export HELM_DEBUG=true

Version Differences

v2 → v3 Migration

Change Description
Tiller removed Client-only architecture
Namespace-scoped Releases per namespace, not cluster-wide
Secrets storage Default changed from ConfigMaps
Chart API v2 New chart format
Release names Unique per namespace only
Commands deleteuninstall, inspectshow

v3 → v4 Changes

Change Description
Plugin system WebAssembly-based runtime
Flag renames --atomic--rollback-on-failure
--force--force-replace
Server-side apply Default for resource updates
OCI digest Full OCI registry support
Chart v3 Coming soon (v2 still works)

Quirks & Gotchas

Values Precedence

Warning: --set always overrides --values files, even if --values comes after.

  • Multiple --values files: rightmost wins
  • --reuse-values preserves previous --set overrides

Example:

# Initial install
helm install myapp ./chart --set replicas=5

# This upgrade keeps replicas=5 (not 3!)
helm upgrade myapp ./chart --reuse-values -f values.yaml  # values.yaml has replicas: 3

Hook Lifecycle

Warning: Hooks are NOT part of the release.

  • Not tracked by helm list
  • Not deleted by helm uninstall
  • Must clean up manually if persistent
  • Hook failures cause release failure

CRD Limitations

Warning: CRDs in crds/ have special handling.

  • Cannot be templated
  • Never upgraded by Helm
  • Never deleted by Helm
  • Must manage upgrades manually

Example:

# CRDs upgraded manually
kubectl apply -f crds/

# Then upgrade chart
helm upgrade myapp ./chart

Namespace Context

Info: Helm 3+ uses current kubectl namespace.

  • Releases are namespace-scoped
  • Always verify: kubectl config get-contexts
  • Use -n NAMESPACE to be explicit

Storage Backend

  • Default: Secrets (Helm 3+)
  • Release data stored in cluster
  • Large releases can hit Secret size limits (1MB)
  • Consider ConfigMaps for very large releases

Change storage backend:

export HELM_DRIVER=configmap

Also see

Helm Cheatsheet - NexusCS