Packaging with Helm
Once you start running real apps on Kubernetes, you quickly notice a problem: every app needs a pile of YAML files (Deployment, Service, ConfigMap, Secret, Ingress, and more), and most of those files look almost the same except for a few values like the image name or the number of replicas. Copy-pasting and hand-editing all that YAML is slow and error-prone. Helm is the package manager for Kubernetes — it bundles all those YAML files into a single reusable, versioned package called a chart, so you can install a whole application with one command. Think of it like apt on Ubuntu: instead of compiling software by hand, you run apt install and get a ready-to-run package. Helm does the same thing, but for Kubernetes apps.
What problem does Helm solve?
A Kubernetes app is rarely one file. A typical database deployment needs:
- a Deployment or StatefulSet (which Pods to run)
- a Service (a stable network address for those Pods)
- a ConfigMap (non-secret settings)
- a Secret (passwords and keys)
- a PersistentVolumeClaim (storage that survives restarts)
If you manage these by hand, you copy them between projects and edit the same fields over and over. When you need a staging copy with a different password and smaller resources, you copy everything again. That copy-pasting is where mistakes creep in — a forgotten label, a mismatched name, a wrong namespace.
Helm replaces copy-pasting with templating (writing the YAML once with blanks, then filling the blanks from a values file). You define the shape of the YAML once, and supply the changing values separately. One chart can produce a dev install, a staging install, and a production install — same templates, different values.
Key Helm concepts
| Term | What it means |
|---|---|
| Chart | A package: a folder (or .tgz archive) of templated Kubernetes YAML plus metadata. |
| values.yaml | The default settings for a chart (image tag, replicas, passwords, sizes). You override these at install time. |
| Release | One installed instance of a chart in your cluster. You can install the same chart many times under different release names. |
| Repository | A web server hosting many charts, added with helm repo add. Like an apt repository. |
| Template | A YAML file with placeholders (e.g. {{ .Values.image.tag }}) that Helm fills in to produce final Kubernetes manifests. |
When to use Helm: any time you deploy off-the-shelf software (databases, message queues, monitoring), or any time your own app’s YAML differs only by environment. When NOT to use it: for a tiny one-Pod experiment, plain kubectl apply -f is simpler and you don’t need the extra layer.
Installing Helm on Ubuntu
Helm is a single command-line tool that runs on your machine and talks to your cluster using your existing kubectl config. On Ubuntu 22.04 / 24.04 LTS, install it from the official apt repository:
curl https://baltocdn.com/helm/signing.asc | sudo gpg --dearmor -o /usr/share/keyrings/helm.gpg
sudo apt-get install -y apt-transport-https
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install -y helm
Check the install:
helm version
Output:
version.BuildInfo{Version:"v3.16.2", GitCommit:"13654a5...", GitTreeState:"clean", GoVersion:"go1.22.7"}
Helm 3 (the current major version) has no server-side component. Older guides mention “Tiller”, a pod that ran in your cluster — that was Helm 2 and it was a real security risk because it held cluster-wide permissions. Helm 3 removed it entirely. If a tutorial tells you to
helm initor install Tiller, it is out of date — ignore it.
Installing a chart: a PostgreSQL database
Let’s install a real application — a PostgreSQL database from the Bitnami repository. First, add the repository and refresh the local index:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
Output:
"bitnami" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
Update Complete. ⎈Happy Helming!⎈
Before installing, look at what settings the chart exposes. The helm show values command prints the chart’s full values.yaml:
helm show values bitnami/postgresql | head -n 40
Now install it. We give the release the name mydb and override two values: the database password and the storage size. The --set flag overrides one value inline:
helm install mydb bitnami/postgresql \
--set auth.postgresPassword=ChangeMe123 \
--set primary.persistence.size=8Gi
Output:
NAME: mydb
LAST DEPLOYED: Mon Jun 15 10:42:01 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
PostgreSQL can be accessed via port 5432 on the following DNS name from within your cluster:
mydb-postgresql.default.svc.cluster.local
Confirm the release and the Pods it created:
helm list
kubectl get pods
Output:
NAME NAMESPACE REVISION STATUS CHART APP VERSION
mydb default 1 deployed postgresql-16.2.1 17.4.0
NAME READY STATUS RESTARTS AGE
mydb-postgresql-0 1/1 Running 0 48s
Using a values file instead of many —set flags
For more than a couple of overrides, put them in a file. This is cleaner, reviewable in Git, and reusable across environments. Create mydb-values.yaml:
auth:
postgresPassword: ChangeMe123
database: appdb
primary:
persistence:
size: 8Gi
resources:
requests:
cpu: 250m
memory: 256Mi
Install (or change an existing release) with the file:
helm upgrade --install mydb bitnami/postgresql -f mydb-values.yaml
helm upgrade --install is the safe everyday command: it installs the release if it does not exist, or updates it in place if it does. Each change bumps the revision number, so you can roll back:
helm rollback mydb 1
Never put real passwords in a values file that you commit to Git. Use Kubernetes Secrets, a secrets manager, or
--setfrom an environment variable at install time. A password in your repo history is a password leaked forever.
When you are done, remove everything the chart created with one command:
helm uninstall mydb
Best Practices
- Use
helm upgrade --installeverywhere (including first install) so the same command works in scripts and CI/CD. - Keep a values file per environment (
values-dev.yaml,values-prod.yaml) under version control, but keep secrets out of those files. - Pin the chart version with
--versionso installs are reproducible and don’t silently upgrade. - Run
helm template ./mychartorhelm install --dry-run --debugto preview the rendered YAML before touching the cluster. - Use
helm rollbackinstead of editing live resources by hand — it keeps your release history honest. - Read the chart’s
NOTESoutput andhelm show valuesbefore installing; defaults are often not what you want for production.