Helm Basics: Kubernetes Package Manager
Learn Helm fundamentals: helm install, charts, values.yaml, templating, releases, and when to choose Helm over plain Kubernetes YAML manifests.
What you'll learn
- ✓What Helm is and the problem it solves
- ✓How to install, upgrade, and roll back releases
- ✓The structure of a chart and the role of values.yaml
- ✓How Go templating renders manifests with values and helpers
- ✓When Helm is the right tool and when raw YAML is better
Prerequisites
- •A working Kubernetes cluster and kubectl
- •Familiarity with [pods, deployments, and services](/blog/kubernetes-pods-deployments-services)
- •Comfort with [configmaps and secrets](/blog/kubernetes-configmaps-and-secrets)
Once you have written a few Kubernetes deployments, services, and configmaps by hand, the next problem appears: how do you package them so they can be installed by someone else, parameterized for different environments, and rolled back cleanly? Helm is the most widely used answer. It is the de facto package manager for Kubernetes.
This article walks through what a Helm chart is, how the templating system works, and how you operate on releases — plus an honest take on when Helm helps and when plain YAML is enough.
Installing Helm
Helm is a single client-side binary that talks to your cluster through the existing kubeconfig. On macOS or Linux:
brew install helm
# or
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
There is no server-side component. Helm 3 stores release state in Kubernetes Secrets inside the namespace where the release lives.
Installing a chart from a repository
The fastest way to see Helm in action is to install a public chart:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install my-redis bitnami/redis
What just happened:
- Helm pulled the
redischart from thebitnamirepo. - It rendered the chart’s templates into final Kubernetes manifests using default values.
- It applied those manifests to the current namespace.
- It stored a record of this release named
my-redis.
You can inspect everything that got created:
helm list
helm status my-redis
kubectl get all -l app.kubernetes.io/instance=my-redis
Releases, upgrades, and rollbacks
A release is a specific installation of a chart. The same chart can be installed many times under different release names, in different namespaces, with different values. Each install or upgrade creates a new revision.
helm upgrade my-redis bitnami/redis --set auth.password=newsecret
helm history my-redis
helm rollback my-redis 1
helm rollback reverts to a previous revision in seconds. This is one of the underrated wins of Helm: a single command undoes a complex multi-resource change. Doing the same with raw YAML requires keeping the previous manifests and reapplying them manually.
To remove a release entirely:
helm uninstall my-redis
This deletes all resources the chart created, plus the stored release record.
The chart structure
A chart is a directory laid out like this:
mychart/
Chart.yaml # name, version, appVersion, dependencies
values.yaml # default configuration values
templates/
deployment.yaml
service.yaml
configmap.yaml
_helpers.tpl # reusable template snippets
NOTES.txt # message shown after install
charts/ # subcharts (dependencies)
.helmignore
Chart.yaml is metadata. values.yaml is the configuration surface that operators of your chart see. Everything in templates/ is processed by Helm’s templating engine before being applied to the cluster.
A minimal Chart.yaml:
apiVersion: v2
name: mychart
description: An example chart
type: application
version: 0.1.0
appVersion: "1.0.0"
Templating with values
Inside templates/, files are Go templates with access to a .Values object populated from values.yaml and any overrides passed at install or upgrade time.
A trivial deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-app
labels:
app.kubernetes.io/name: {{ include "mychart.name" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "mychart.name" . }}
template:
metadata:
labels:
app: {{ include "mychart.name" . }}
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.service.port }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
The matching values.yaml:
replicaCount: 2
image:
repository: myorg/app
tag: "1.2.3"
service:
port: 8080
env:
LOG_LEVEL: info
FEATURE_FLAG_X: "true"
At install time, anyone can override values:
helm install app ./mychart --set replicaCount=5 --set image.tag=1.2.4
# or
helm install app ./mychart -f production.values.yaml
The include and _helpers.tpl pattern lets you define reusable snippets — typically a name helper that respects release name length limits and a labels helper applied to every resource.
values.yaml as the API of a chart
The most important design choice when authoring a chart is the shape of values.yaml. It is the public interface other people interact with. Some rules of thumb:
- Group related settings under namespaced keys:
database.host,database.port, notdbHost,dbPort. - Provide sensible defaults so
helm installworks with no flags. - Document every key with a comment. The defaults file is the documentation.
- Resist exposing every Kubernetes field — pick the ones operators actually need to change.
A small, well-thought-out values.yaml makes a chart easy to adopt. A sprawling one with hundreds of knobs makes upgrades terrifying.
Working with secrets and configmaps
Helm renders configmaps and secrets the same way it renders any other resource. If you are already using Kubernetes configmaps and secrets, you can drive them from values:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
data:
{{- toYaml .Values.config | nindent 2 }}
For real secrets, do not put them in values.yaml and commit it. Common patterns:
- Inject at install time via
--setfrom a CI secret store. - Use
helm-secretswith SOPS for encrypted-at-rest values files. - Reference an external secret manager via the External Secrets Operator and ignore Helm for the secret itself.
When to use Helm and when not
Helm shines when:
- You need to distribute a multi-resource application to other teams or the public.
- You run the same app in many environments with different configuration.
- You want one-command rollbacks across many resources.
- You depend on community charts for off-the-shelf software (Postgres, Prometheus, Redis).
It is less of a win when:
- You have one simple app deployed by one team in one cluster, where
kubectl apply -kwith Kustomize is lighter. - You want strict, declarative GitOps with no client-side templating — tools like Argo CD with Kustomize or plain manifests fit better.
- The templating starts to dominate the actual YAML, signaling that the configuration shape needs rethinking.
A pragmatic split: use community Helm charts for third-party software, and decide per-application whether your own services need Helm or just Kustomize-style overlays.
Wrap up
Helm packages Kubernetes manifests into versioned, parameterized, releasable units. Learn the install / upgrade / rollback loop first, then dig into chart authoring and the templating engine. Treat values.yaml as a public API and keep it small.
Combined with the resources covered in pods, deployments, and services and configmaps and secrets, Helm becomes the layer that turns a pile of YAML into something you can ship, share, and confidently roll back.