Kubernetes Helm: Package Manager for Kubernetes
If you've managed Kubernetes applications in production, you've experienced the pain of YAML proliferation. A single microservice might require a Deployment, Service, ConfigMap, Secret, Ingress,...
Key Insights
- Helm eliminates YAML sprawl by packaging Kubernetes manifests into reusable, versioned charts with templating support that adapts to different environments
- The three-tier architecture of charts (packages), releases (deployments), and repositories (distribution) mirrors traditional package managers like apt or npm
- Production Helm usage requires discipline around values management, dependency locking, and treating charts as versioned artifacts with proper testing pipelines
Introduction to Helm
If you’ve managed Kubernetes applications in production, you’ve experienced the pain of YAML proliferation. A single microservice might require a Deployment, Service, ConfigMap, Secret, Ingress, ServiceAccount, and various RBAC resources. Multiply that by dozens of services across multiple environments, and you’re drowning in nearly-identical manifests with subtle differences.
Helm solves this by introducing templating and packaging to Kubernetes. Think of it as apt, yum, or npm for Kubernetes—a package manager that bundles related resources together, parameterizes configuration, and provides versioning and rollback capabilities. Instead of maintaining separate YAML files for dev, staging, and production, you maintain one templated chart and override values per environment.
The alternative is managing raw manifests with tools like Kustomize or custom scripts. While Kustomize has its place for simple overlays, Helm’s templating engine and ecosystem of pre-built charts make it the de facto standard for complex deployments.
Core Helm Concepts
Helm operates on three fundamental concepts that work together to manage Kubernetes applications.
Charts are the packaging format. A chart is a directory structure containing templated Kubernetes manifests, metadata, and default values. Charts can be versioned, shared, and distributed through repositories.
Releases are running instances of charts. When you install a chart, Helm creates a release with a unique name. You can have multiple releases of the same chart in a cluster, each with different configurations.
Repositories are collections of packaged charts. Public repositories like Artifact Hub host thousands of community charts, while private repositories let you distribute internal applications.
A basic chart directory looks like this:
mychart/
├── Chart.yaml # Metadata about the chart
├── values.yaml # Default configuration values
├── charts/ # Dependent charts
└── templates/ # Kubernetes manifest templates
├── deployment.yaml
├── service.yaml
├── _helpers.tpl # Template helpers
└── NOTES.txt # Post-installation notes
The templates/ directory contains Go template files that generate Kubernetes manifests. The values.yaml file provides default configuration, which users override during installation.
Installing and Using Helm Charts
Install Helm from the official releases or use a package manager:
# macOS
brew install helm
# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
Add a chart repository and search for available charts:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo postgresql
Install PostgreSQL with custom values:
helm install my-postgres bitnami/postgresql \
--set auth.postgresPassword=secretpassword \
--set primary.persistence.size=20Gi \
--namespace database \
--create-namespace
The --set flag overrides individual values. For complex configurations, use a values file:
helm install my-postgres bitnami/postgresql \
-f custom-values.yaml \
--namespace database
Manage releases with these commands:
# List all releases
helm list --all-namespaces
# Check release status
helm status my-postgres -n database
# Upgrade with new values
helm upgrade my-postgres bitnami/postgresql \
--set primary.persistence.size=50Gi \
-n database
# View release history
helm history my-postgres -n database
# Uninstall
helm uninstall my-postgres -n database
Creating Custom Helm Charts
Generate a chart scaffold:
helm create myapp
This creates a complete chart structure with example templates. Start by editing Chart.yaml:
apiVersion: v2
name: myapp
description: A Helm chart for my application
type: application
version: 0.1.0
appVersion: "1.0.0"
The version field tracks chart changes, while appVersion tracks the application version.
Define default values in values.yaml:
replicaCount: 2
image:
repository: mycompany/myapp
pullPolicy: IfNotPresent
tag: "1.0.0"
service:
type: ClusterIP
port: 8080
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
ingress:
enabled: false
className: nginx
host: myapp.example.com
Create a templated deployment in 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 }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
The double curly braces {{ }} denote template directives. .Values accesses the values hierarchy, .Chart accesses chart metadata, and functions like toYaml format data appropriately.
Helm Templating and Values
Helm’s templating power comes from Go templates plus Sprig functions. Use conditionals to enable optional features:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "myapp.fullname" . }}
spec:
ingressClassName: {{ .Values.ingress.className }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "myapp.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- end }}
Create environment-specific value files:
values-dev.yaml:
replicaCount: 1
image:
tag: "latest"
ingress:
enabled: true
host: myapp-dev.internal
values-prod.yaml:
replicaCount: 3
image:
tag: "1.0.0"
ingress:
enabled: true
host: myapp.example.com
resources:
limits:
cpu: 1000m
memory: 1Gi
Install with environment-specific values:
helm install myapp-prod ./myapp -f values-prod.yaml -n production
Debug templates without installing:
helm template myapp ./myapp -f values-dev.yaml
This renders templates locally, perfect for validating syntax before deployment.
Helm Chart Dependencies and Lifecycle
Charts can depend on other charts. Define dependencies in 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 ./myapp
This downloads dependent charts into the charts/ directory. Override dependent chart values in your values.yaml:
postgresql:
enabled: true
auth:
database: myapp
username: myapp
redis:
enabled: true
architecture: standalone
Helm tracks release history, enabling rollbacks:
# Upgrade gone wrong
helm upgrade myapp-prod ./myapp -f values-prod.yaml
# Rollback to previous revision
helm rollback myapp-prod
# Rollback to specific revision
helm rollback myapp-prod 3
Each upgrade creates a new revision. Helm stores release data in Kubernetes Secrets by default, maintaining complete history.
Best Practices and Production Considerations
Lint and test charts before deployment:
helm lint ./myapp
Define tests in templates/tests/:
apiVersion: v1
kind: Pod
metadata:
name: {{ include "myapp.fullname" . }}-test
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: curlimages/curl:latest
command: ['curl']
args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health']
restartPolicy: Never
Run tests after installation:
helm test myapp-prod -n production
Version charts semantically. Increment the patch version for bug fixes, minor for backward-compatible changes, major for breaking changes. Lock chart versions in production:
helm install myapp-prod ./myapp --version 1.2.3
Never store secrets in values files. Use external secret management:
# Reference external secrets
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: db-password
Or use tools like sealed-secrets or external-secrets-operator to manage sensitive data.
Use .helmignore to exclude unnecessary files from packaged charts:
.git/
.gitignore
*.md
.DS_Store
Pin dependency versions in production. Avoid ranges like "^1.0.0" that could introduce unexpected changes.
Helm transforms Kubernetes complexity into manageable, versioned packages. While it adds another layer to learn, the payoff in reduced YAML duplication and improved deployment consistency makes it essential for any serious Kubernetes operation. Start with public charts to understand patterns, then build custom charts as your applications mature.