Helm is the package manager for Kubernetes. If you have ever run apt install on Debian or brew install on macOS, Helm does the same thing for Kubernetes applications. Instead of writing and maintaining dozens of YAML manifests by hand, you define a chart that packages everything together — Deployments, Services, ConfigMaps, Secrets, Ingress rules — and deploy it with a single command.
This guide covers the core Helm workflow: installing charts, upgrading releases, rolling back when things go wrong, and avoiding the mistakes that cause production incidents.
Before diving into commands, here are the terms you will encounter constantly:
| Concept | What It Is |
|---|---|
| Chart | A package of Kubernetes manifests with templating. Think of it as a "recipe" for deploying an application |
| Release | A specific instance of a chart deployed to a cluster. You can install the same chart multiple times with different release names |
| Repository | A server that hosts charts. Bitnami, Artifact Hub, and your company's internal registry are all repositories |
| Values | Configuration parameters that customize a chart. Defined in values.yaml or passed via --set |
| Revision | A version of a release. Every helm install or helm upgrade creates a new revision. Revisions enable rollback |
The typical workflow is: add a repository, search for a chart, install it with custom values, upgrade when configuration changes, and rollback if something breaks.
The most common pattern. Add the repo first, then install:
# Add the Bitnami repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# Install nginx with a release name
helm install my-nginx bitnami/nginx
This creates a release called my-nginx in the current namespace. Helm renders the chart templates with default values and applies the resulting manifests to the cluster.
When you are developing your own chart or have downloaded one:
# Install from a local directory
helm install my-app ./my-chart
# Install from a .tgz archive
helm install my-app ./my-chart-1.2.0.tgz
Charts almost always need customization. There are two ways to pass values:
Using a values file (-f) — best for complex or reusable configurations:
helm install my-nginx bitnami/nginx -f values.yaml
Where values.yaml might look like:
replicaCount: 3
service:
type: LoadBalancer
port: 80
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
Tip: Use the YAML/JSON Converter to quickly validate and format your values files.
Using --set — best for quick overrides:
helm install my-nginx bitnami/nginx \
--set replicaCount=3 \
--set service.type=LoadBalancer
You can combine both. --set values take precedence over -f values:
helm install my-nginx bitnami/nginx \
-f values.yaml \
--set replicaCount=5
Always deploy into a specific namespace rather than relying on default:
helm install my-nginx bitnami/nginx \
-n production \
--create-namespace \
-f values-prod.yaml
--create-namespace creates the namespace if it does not exist, which is useful in CI/CD pipelines where the namespace might not be pre-provisioned.
Always pin the chart version in production. Without it, Helm installs the latest version, which might include breaking changes:
helm install my-nginx bitnami/nginx --version 18.2.4 -f values.yaml
Check available versions:
helm search repo bitnami/nginx --versions
When you need to change configuration or upgrade the chart version, use helm upgrade:
# Upgrade with new values
helm upgrade my-nginx bitnami/nginx -f values-updated.yaml
# Upgrade to a new chart version
helm upgrade my-nginx bitnami/nginx --version 18.3.0
In production, always use --atomic. If the upgrade fails (pods crash, health checks fail), Helm automatically rolls back to the previous revision:
helm upgrade my-nginx bitnami/nginx \
--atomic \
--timeout 5m \
-f values-prod.yaml
Without --atomic, a failed upgrade leaves the release in a broken FAILED state that you have to clean up manually. With it, you either get a successful upgrade or an automatic rollback to the last known-good state.
This is one of the most confusing aspects of Helm and a common source of bugs:
| Flag | Behavior |
|---|---|
--reuse-values | Merges the previous release's values with any new --set or -f values. Existing values are preserved |
--reset-values | Starts from the chart's default values, then applies --set or -f values. Previous customizations are discarded |
| Neither (default) | Same as --reset-values — starts from chart defaults |
The dangerous scenario: You installed with -f values.yaml containing 20 custom settings. Later you run helm upgrade my-nginx bitnami/nginx --set image.tag=1.25 without --reuse-values. Result: all 20 custom settings revert to defaults. Only image.tag is set.
Best practice: Always pass the full values file on every upgrade. Do not rely on --reuse-values — it can silently merge stale values from previous revisions:
# Safe: explicit values every time
helm upgrade my-nginx bitnami/nginx -f values-prod.yaml --set image.tag=1.25
Use --set | Use -f values.yaml |
|---|---|
| Quick one-off overrides in the terminal | Production deployments |
| CI/CD for simple key=value changes | Complex nested values |
| Debugging or testing | Values you want in version control |
| Single scalar values | Lists, maps, multiline strings |
Nested values in --set get ugly fast. Compare:
# This is hard to read and error-prone
helm install my-app ./chart \
--set ingress.enabled=true \
--set ingress.hosts[0].host=app.example.com \
--set ingress.hosts[0].paths[0].path=/ \
--set ingress.hosts[0].paths[0].pathType=Prefix \
--set ingress.tls[0].secretName=app-tls \
--set ingress.tls[0].hosts[0]=app.example.com
Put that in a values file instead. It is easier to read, review, and track in Git.
Before rolling back, check the revision history:
helm history my-nginx -n production
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Mon Feb 10 09:00:00 2026 superseded nginx-18.2.4 1.25.4 Install complete
2 Wed Feb 12 14:30:00 2026 superseded nginx-18.3.0 1.25.5 Upgrade complete
3 Thu Feb 13 10:15:00 2026 deployed nginx-18.3.0 1.25.5 Upgrade complete
# Rollback to revision 2
helm rollback my-nginx 2 -n production
# Rollback to the immediately previous revision
helm rollback my-nginx 0 -n production
Revision 0 is a special value meaning "the previous revision."
Rollback when:
Fix-forward when:
Tip: Use the Helm CLI Builder to construct rollback, history, and status commands interactively.
helm template renders the chart templates locally without sending anything to the cluster. This is invaluable for catching errors:
# Render templates to stdout
helm template my-nginx bitnami/nginx -f values.yaml
# Render and save to a file for inspection
helm template my-nginx bitnami/nginx -f values.yaml > rendered.yaml
# Render a specific template
helm template my-nginx bitnami/nginx -f values.yaml -s templates/deployment.yaml
Combine helm template with kubectl dry-run for full validation:
# Render and validate against the cluster API
helm template my-nginx bitnami/nginx -f values.yaml | kubectl apply --dry-run=server -f -
This catches issues that template rendering alone cannot — like invalid resource names, missing CRDs, or namespace conflicts.
If you are developing your own chart, use helm lint to catch template errors:
helm lint ./my-chart -f values.yaml
Without --atomic, a failed upgrade leaves the release in a FAILED state. Subsequent helm upgrade commands may behave unpredictably. Always use --atomic in production:
helm upgrade my-app ./chart --atomic --timeout 10m -f values-prod.yaml
Inline --set flags are fragile for anything beyond simple scalars. Arrays, nested objects, and strings with special characters break easily. Use -f values.yaml instead.
# Dangerous: installs whatever "latest" is today
helm install my-app bitnami/postgresql
# Safe: pinned version, reproducible
helm install my-app bitnami/postgresql --version 16.3.2
Without version pinning, an automated pipeline can deploy a new chart version with breaking changes without any code change in your repository.
The helm-diff plugin shows what will change before you upgrade. Install it:
helm plugin install https://github.com/databus23/helm-diff
# Preview changes before upgrading
helm diff upgrade my-nginx bitnami/nginx -f values-updated.yaml
This is the Helm equivalent of terraform plan — review changes before applying them.
Always verify the release is healthy:
# Check release status
helm status my-nginx -n production
# Check the actual pods
kubectl get pods -l app.kubernetes.io/instance=my-nginx -n production
Tip: Use the kubectl Builder to construct
get,describe, andlogscommands for verifying deployments.
| Task | Command |
|---|---|
| Add a repository | helm repo add name url |
| Search for charts | helm search repo keyword |
| Install a chart | helm install release chart -f values.yaml --version x.y.z |
| Upgrade a release | helm upgrade release chart --atomic -f values.yaml |
| Rollback | helm rollback release revision |
| View history | helm history release |
| Render templates | helm template release chart -f values.yaml |
| Check status | helm status release |
| Uninstall | helm uninstall release |
| Preview changes | helm diff upgrade release chart -f values.yaml |
| Lint a chart | helm lint ./chart |
| List releases | helm list -A |
Helm simplifies Kubernetes deployments by packaging manifests into versioned, configurable charts. The core workflow is straightforward: install to deploy, upgrade --atomic to update safely, and rollback when something goes wrong.
The biggest mistakes — skipping --atomic, not pinning versions, relying on --set for complex configs — are all avoidable with good habits. Use helm template to debug before deploying, helm diff to preview changes, and always keep your values files in version control.
For hands-on practice: