A production Kubernetes application quickly grows into dozens of YAML files: Deployments, Services, ConfigMaps, Secrets, Ingress rules, RBAC roles, and more.
Managing all of these manually is error-prone. Different environments (dev, staging, production) need different values. Sharing your app with others means sending them a bundle of raw YAML.
Helm solves this. It is the package manager for Kubernetes — think npm for Node.js or apt for Ubuntu, but for Kubernetes applications.
What is Helm?
Helm packages Kubernetes YAML files into charts — versioned, reusable packages. A chart can include everything needed to deploy an application.
Key Helm concepts:
| Term | What it is |
|---|---|
| Chart | A package of Kubernetes YAML templates |
| Release | A deployed instance of a chart |
| Repository | A collection of charts (like a package registry) |
| Values | Configuration that customizes a chart |
A chart can be installed multiple times with different configurations. Each installation is a separate release.
Installing Helm
macOS (Homebrew):
brew install helm
Linux:
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
Windows (winget):
winget install Helm.Helm
Verify:
helm version
Installing an App with Helm
The Bitnami repository has high-quality charts for popular apps. Add it:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
Search for available charts:
helm search repo postgresql
Install PostgreSQL:
helm install my-postgres bitnami/postgresql \
--set auth.username=alex \
--set auth.password=YOUR_SECURE_PASSWORD \
--set auth.database=myappdb
Helm deploys PostgreSQL and all its Kubernetes resources in seconds.
Check the release:
helm list
NAME NAMESPACE REVISION STATUS CHART APP VERSION
my-postgres default 1 deployed postgresql-16.4.12 16.6.0
Check the running Pods:
kubectl get pods
NAME READY STATUS RESTARTS AGE
my-postgres-0 1/1 Running 0 60s
Helm Commands Reference
# Add a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
# Update repository index
helm repo update
# Search for charts
helm search repo postgresql
helm search hub prometheus # Search Artifact Hub (all public repos)
# Install a chart
helm install <release-name> <chart> [--set key=value]
# Install from a values file
helm install my-postgres bitnami/postgresql -f my-values.yaml
# List all releases
helm list
helm list -A # All namespaces
# Check release status
helm status my-postgres
# Upgrade a release
helm upgrade my-postgres bitnami/postgresql --set image.tag=16.6.0
# Upgrade or install (idempotent)
helm upgrade --install my-postgres bitnami/postgresql
# Roll back to previous version
helm rollback my-postgres
# Roll back to a specific revision
helm rollback my-postgres 1
# Delete a release
helm uninstall my-postgres
# Show chart values
helm show values bitnami/postgresql
# Get rendered YAML without deploying (dry run)
helm install my-postgres bitnami/postgresql --dry-run
Using a Values File
Instead of long --set flags, use a values file:
# my-values.yaml
auth:
username: alex
password: YOUR_SECURE_PASSWORD
database: myappdb
primary:
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
persistence:
size: 10Gi
helm install my-postgres bitnami/postgresql -f my-values.yaml
Values files are easier to read, can be version-controlled, and can be overridden per environment.
Creating Your Own Chart
When your app has many YAML files, package them into a Helm chart.
Create a chart scaffold:
helm create myapp
This creates:
myapp/
├── Chart.yaml # Chart metadata (name, version, description)
├── values.yaml # Default configuration values
├── templates/ # Kubernetes YAML templates
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── serviceaccount.yaml
│ └── _helpers.tpl # Template helper functions
└── charts/ # Dependent charts
Chart.yaml
# myapp/Chart.yaml
apiVersion: v2
name: myapp
description: My web application
type: application
version: 0.1.0 # Chart version
appVersion: "1.0.0" # App version
values.yaml
# myapp/values.yaml
replicaCount: 2
image:
repository: myapp
tag: "1.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
Template Syntax
Helm uses Go template syntax. Values from values.yaml are referenced with {{ .Values.key }}:
# myapp/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 }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 80
resources:
{{- toYaml .Values.resources | nindent 12 }}
Install Your Chart
# Install from local directory
helm install myapp ./myapp
# Install with custom values
helm install myapp ./myapp --set replicaCount=3
# Install for staging with a values override
helm install myapp-staging ./myapp -f staging-values.yaml
Lint and Test Your Chart
# Check for errors
helm lint ./myapp
# See rendered YAML without deploying
helm template ./myapp
# Test after installing
helm test myapp
Environment Promotion
A common pattern: one chart, different values per environment.
myapp/
├── values.yaml # Shared defaults
├── values-dev.yaml # Dev overrides
├── values-staging.yaml # Staging overrides
└── values-prod.yaml # Production overrides
# values-prod.yaml
replicaCount: 5
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
# Deploy to production
helm upgrade --install myapp ./myapp -f values.yaml -f values-prod.yaml
Artifact Hub
Artifact Hub (artifacthub.io) is the official directory for Helm charts. It lists charts from many publishers, including CNCF projects and major vendors.
Search for charts before building your own — there is likely already a well-maintained chart for popular software.
# Search from the CLI
helm search hub grafana --output yaml
Common Mistakes
Storing secrets in values.yaml
values.yaml is committed to git. Never put passwords or API keys in it. Use Kubernetes Secrets with secretKeyRef, or integrate with External Secrets Operator. Pass sensitive values via --set in CI/CD pipelines (stored as CI secrets).
# CI/CD: pass the secret at deploy time, not in the values file
helm upgrade --install myapp ./myapp \
-f values-prod.yaml \
--set db.password=$DB_PASSWORD
Not pinning chart versions
Running helm upgrade without pinning the chart version can pull a new chart version with breaking changes.
# Specify the chart version
helm install my-postgres bitnami/postgresql --version 16.4.12
Not using --atomic in CI
If a Helm install fails partway, you get a broken release. Use --atomic to automatically roll back on failure:
helm upgrade --install myapp ./myapp \
-f values-prod.yaml \
--atomic \
--timeout 5m
What’s Next?
Your applications are packaged as Helm charts. The next step is knowing what happens when they run — setting up monitoring with Prometheus and Grafana.
Next: Kubernetes Tutorial #8: Monitoring with Prometheus and Grafana