Raw Kubernetes manifests are fine for small examples. They get noisy fast once an application needs environment-specific values, dependencies, upgrades, and rollbacks. Helm gives that pile of YAML a package format: reusable, versioned charts.
What is Helm?
Helm is often called “the package manager for Kubernetes.” It packages Kubernetes resources into charts - reusable, versioned bundles that can be easily shared and deployed.
Without Helm:
┌─────────────────────────────────────────────────┐
│ Manual Deployment │
│ kubectl apply -f deployment.yaml │
│ kubectl apply -f service.yaml │
│ kubectl apply -f configmap.yaml │
│ kubectl apply -f secret.yaml │
│ kubectl apply -f ingress.yaml │
│ ... (20+ more files) │
│ Manual version tracking │
│ Manual rollbacks │
└─────────────────────────────────────────────────┘
With Helm:
┌─────────────────────────────────────────────────┐
│ Helm Deployment │
│ helm install myapp ./mychart │
│ - Versioned releases │
│ - Easy rollbacks │
│ - Templated configurations │
│ - Dependency management │
└─────────────────────────────────────────────────┘
Helm Benefits
- Simplified Deployments: Single command deploys entire applications
- Version Control: Track releases and rollback easily
- Templating: Parameterize manifests for different environments
- Dependency Management: Bundle related charts together
- Sharing: Public repositories for common applications
Helm Architecture
┌─────────────────────────────────────────────────┐
│ Helm CLI │
│ ┌─────────────────────────────────────┐ │
│ │ helm install/upgrade/rollback │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Chart + Values │ │
│ │ templates/ + values.yaml │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Kubernetes API Server │ │
│ │ (manifests applied) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Installing Helm
# macOS
brew install helm
# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Windows (Chocolatey)
choco install kubernetes-helm
# Verify installation
helm version
Helm Charts
Chart Structure
mychart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration values
├── charts/ # Chart dependencies
├── templates/ # Kubernetes manifest templates
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── _helpers.tpl # Template helpers
│ └── NOTES.txt # Post-install notes
└── .helmignore # Files to ignore
Chart.yaml
# Chart.yaml
apiVersion: v2
name: webapp
description: A Helm chart for deploying web applications
type: application
version: 1.0.0
appVersion: "2.0.0"
keywords:
- webapp
- nodejs
maintainers:
- name: Platform Team
email: [email protected]
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
values.yaml
# values.yaml
replicaCount: 3
image:
repository: myapp
tag: "v1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
postgresql:
enabled: true
auth:
database: webapp
username: webapp
env:
LOG_LEVEL: info
CACHE_ENABLED: "true"
Template Examples
deployment.yaml:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "webapp.fullname" . }}
labels:
{{- include "webapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "webapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "webapp.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
service.yaml:
# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "webapp.fullname" . }}
labels:
{{- include "webapp.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
selector:
{{- include "webapp.selectorLabels" . | nindent 4 }}
_helpers.tpl:
# templates/_helpers.tpl
{{- define "webapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "webapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- define "webapp.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "webapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
Working with Charts
Installing Charts
# Install from local directory
helm install myapp ./mychart
# Install with custom values
helm install myapp ./mychart -f custom-values.yaml
# Install with inline values
helm install myapp ./mychart --set replicaCount=5 --set image.tag=v2.0.0
# Install in specific namespace
helm install myapp ./mychart -n production --create-namespace
# Install from repository
helm install nginx ingress-nginx/ingress-nginx
# Dry run (preview without installing)
helm install myapp ./mychart --dry-run --debug
Upgrading Releases
# Upgrade with new values
helm upgrade myapp ./mychart -f production-values.yaml
# Upgrade with inline values
helm upgrade myapp ./mychart --set image.tag=v2.0.0
# Install or upgrade (idempotent)
helm upgrade --install myapp ./mychart
# Upgrade with atomic rollback on failure
helm upgrade myapp ./mychart --atomic --timeout 5m
Rolling Back
# View release history
helm history myapp
# Rollback to previous version
helm rollback myapp
# Rollback to specific revision
helm rollback myapp 2
Managing Releases
# List releases
helm list
helm list -A # All namespaces
# Get release status
helm status myapp
# Get release values
helm get values myapp
helm get values myapp --all
# Get rendered manifests
helm get manifest myapp
# Uninstall release
helm uninstall myapp
helm uninstall myapp --keep-history
Helm Repositories
# Add repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable https://charts.helm.sh/stable
# Update repositories
helm repo update
# Search repositories
helm search repo nginx
helm search repo bitnami/postgresql --versions
# Search Artifact Hub
helm search hub wordpress
Environment-Specific Values
# values-development.yaml
replicaCount: 1
image:
tag: "latest"
resources:
requests:
memory: "64Mi"
cpu: "50m"
ingress:
hosts:
- host: dev.myapp.example.com
# values-production.yaml
replicaCount: 5
image:
tag: "v1.0.0"
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
ingress:
hosts:
- host: myapp.example.com
# Deploy to different environments
helm install myapp ./mychart -f values-development.yaml -n development
helm install myapp ./mychart -f values-production.yaml -n production
Chart Dependencies
# Chart.yaml
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "17.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
# Update dependencies
helm dependency update ./mychart
# Build dependencies
helm dependency build ./mychart
Creating Charts
# Create new chart
helm create mychart
# Package chart
helm package ./mychart
# Lint chart
helm lint ./mychart
# Template locally (debug)
helm template myapp ./mychart --debug
Helm Hooks
Execute actions at specific points in release lifecycle:
# templates/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "webapp.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./migrate.sh"]
restartPolicy: Never
Hook Types
| Hook | Description |
|---|---|
| pre-install | Before resources installed |
| post-install | After resources installed |
| pre-upgrade | Before upgrade |
| post-upgrade | After upgrade |
| pre-rollback | Before rollback |
| post-rollback | After rollback |
| pre-delete | Before deletion |
| post-delete | After deletion |
Troubleshooting
# Debug installation
helm install myapp ./mychart --debug --dry-run
# Check release status
helm status myapp
# View release history
helm history myapp
# Get rendered templates
helm get manifest myapp
# Lint chart
helm lint ./mychart
# Template locally
helm template myapp ./mychart > rendered.yaml
Handy commands
# Repository Management
helm repo add NAME URL
helm repo update
helm repo list
helm search repo KEYWORD
# Release Management
helm install RELEASE CHART
helm upgrade RELEASE CHART
helm rollback RELEASE [REVISION]
helm uninstall RELEASE
helm list
helm status RELEASE
helm history RELEASE
# Chart Development
helm create NAME
helm package CHART
helm lint CHART
helm template RELEASE CHART
helm dependency update CHART
# Values
helm get values RELEASE
helm install RELEASE CHART -f values.yaml
helm install RELEASE CHART --set key=value
What matters in practice
- Helm simplifies Kubernetes application deployment
- Charts package related resources together
- Values enable environment-specific configuration
- Templates use Go templating for dynamic manifests
- Releases track deployed chart versions
- Hooks execute actions at lifecycle points
- Dependencies manage chart relationships
Where to go next
Helm handles packaging and release lifecycle. The next layer is Operators, where custom controllers manage application behavior inside the cluster.