Kubernetes Helm: Package Manager for Kubernetes (2026)
Helm is the package manager for Kubernetes — it bundles related Kubernetes manifests into versioned charts, manages dependencies between charts, handles upgrades and rollbacks, and lets you customise deployments through values files. If you're deploying any non-trivial application to Kubernetes, Helm saves enormous time.
Table of Contents
1. Core Concepts
- Chart: A package of Kubernetes manifests with templates and default values
- Release: An installed instance of a chart. You can install the same chart multiple times with different release names
- Repository: A place to store and share charts (like npm registry for Kubernetes)
- Values: Configuration that customises a chart's templates
# Install Helm 3
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Verify
helm version
2. Installing Charts
# Add a repo
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add cert-manager https://charts.jetstack.io
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Search for charts
helm search repo nginx
helm search hub wordpress # searches Artifact Hub
# Install with default values
helm install my-nginx ingress-nginx/ingress-nginx -n ingress-nginx --create-namespace
# Install with custom values
helm install my-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.replicaCount=2 \
--set controller.service.type=LoadBalancer
# Install from a values file
helm install my-nginx ingress-nginx/ingress-nginx \
-f my-nginx-values.yaml \
--namespace ingress-nginx --create-namespace
# Dry run (preview manifests without installing)
helm install my-nginx ingress-nginx/ingress-nginx --dry-run --debug
3. Chart Structure
helm create myapp # scaffolds a new chart
# Generated structure:
myapp/
├── Chart.yaml # Chart metadata (name, version, dependencies)
├── values.yaml # Default configuration values
├── charts/ # Chart dependencies (subcharts)
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ ├── serviceaccount.yaml
│ ├── _helpers.tpl # Reusable template functions
│ └── NOTES.txt # Post-install instructions
└── .helmignore
Chart.yaml with dependencies:
apiVersion: v2
name: myapp
description: My application Helm chart
type: application
version: 1.2.0 # Chart version
appVersion: "2.4.1" # App version (for display)
dependencies:
- name: postgresql
version: "13.2.0"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: "18.0.0"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# Download dependencies
helm dependency update myapp/
4. Template Syntax
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
- containerPort: {{ .Values.service.port }}
{{- if .Values.resources }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
{{- end }}
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
Reusable helpers in _helpers.tpl:
{{/* Generate the full name */}}
{{- define "myapp.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/* Common labels */}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
5. Values and Overrides
# values.yaml — defaults
replicaCount: 1
image:
repository: myregistry/myapp
tag: "" # defaults to Chart.AppVersion
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
env: []
postgresql:
enabled: true
auth:
database: myapp
# Override for production
helm install myapp ./myapp \
-f values.yaml \
-f values-production.yaml \ # production overrides
--set image.tag=1.2.3 \
--set replicaCount=3
# Show computed values
helm get values myapp-release
helm get manifest myapp-release # see rendered manifests
6. Upgrade, Rollback and Uninstall
# Upgrade — applies changes and bumps revision
helm upgrade myapp-release ./myapp \
--set image.tag=1.3.0 \
--atomic \ # rolls back automatically if upgrade fails
--timeout 5m \
--wait # wait for all pods to be ready
# View release history
helm history myapp-release
# Rollback to previous revision
helm rollback myapp-release 2 # rollback to revision 2
helm rollback myapp-release 0 # rollback to previous
# Uninstall (removes all K8s resources, keeps history by default)
helm uninstall myapp-release
helm uninstall myapp-release --keep-history # preserve history for rollback
7. Helm Hooks
Hooks run Jobs at specific points in the release lifecycle:
# templates/db-migrate-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"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: ["python", "manage.py", "migrate"]
restartPolicy: Never
8. Helmfile
Helmfile declaratively manages multiple Helm releases across environments:
# helmfile.yaml
environments:
staging:
values: ["envs/staging.yaml"]
production:
values: ["envs/production.yaml"]
releases:
- name: ingress-nginx
namespace: ingress-nginx
chart: ingress-nginx/ingress-nginx
version: 4.9.1
values:
- controller.replicaCount: 2
- name: cert-manager
namespace: cert-manager
chart: jetstack/cert-manager
version: 1.14.0
set:
- name: installCRDs
value: true
- name: myapp
namespace: production
chart: ./charts/myapp
values:
- values/myapp-{{ .Environment.Name }}.yaml
helmfile sync --environment production # apply all releases
helmfile diff --environment staging # preview changes
helmfile destroy # remove all releases
Frequently Asked Questions
What is the difference between helm install and helm upgrade --install?
helm install fails if the release already exists. helm upgrade --install installs if it doesn't exist, upgrades if it does — perfect for CI/CD pipelines where you want idempotent deploys.
How do I debug template rendering errors?
Use helm template ./myapp to render templates locally without installing. Use --debug for verbose output. Use helm lint ./myapp to catch common mistakes before deploying.
How do I handle secrets in Helm?
Don't store plaintext secrets in values.yaml. Options: use Helm Secrets plugin (encrypts with SOPS/age), reference existing Kubernetes Secrets with valueFrom.secretKeyRef in templates, use External Secrets Operator to sync from AWS Secrets Manager, or inject via CI/CD pipeline with --set secret.value=.
What is the difference between Helm 2 and Helm 3?
Helm 3 (current) removed Tiller (the server-side component) — it uses your local kubeconfig credentials directly. This eliminates RBAC issues and makes Helm cluster-admin no longer required. Helm 2 is EOL and should not be used.