Part of the series Kubernetes basics

Helm Explained Simply: From first install to shipping your own chart

Helm installs and manages a whole Kubernetes application as one package. Learn the problem it solves and what happens between your command and running Pods. Then build, install, upgrade, and ship a small chart step by step.

, 17 min read

Imagine you are deploying OrderUp, an online food-ordering app. To run it on Kubernetes, it needs:

  • a Deployment to run the app’s containers,
  • a Service so other components can reach those containers,
  • a ConfigMap for settings,
  • a Secret for passwords and keys,
  • an Ingress to route website requests to the Service.

You can write each of these as a separate YAML file. Helm helps you organise them, change their settings per environment, and install, upgrade, or remove them together.

We will use OrderUp throughout. Part 1 explains the ideas. Part 2 is a hands-on lab with commands you can run.

Part 1: Understand the concepts

1. The problem without Helm

Suppose OrderUp has four files:

deployment.yaml
service.yaml
configmap.yaml
ingress.yaml

You deploy them with:

kubectl apply -f .

That works. Now you need three environments:

Environment Purpose App copies
Development Developers test changes 1
Testing The team checks the app 2
Production Real customers use it 3

Each environment may also need a different domain, more memory, or other settings. You could copy the four files into three folders. But when the container port changes, you must now edit several copies and get every one right.

The real problem is repeated configuration and the effort of keeping the copies consistent.

Think of a restaurant order form. The form stays the same; each customer’s choices change. Helm works the same way:

The templates stay the same. The values change.

2. Helm is a package manager for Kubernetes

If you have used Ubuntu, you already know the idea. You do not download Python, unzip it, and set paths by hand. You run:

apt update
apt install python3

Helm does the same job for Kubernetes applications:

helm repo add podinfo https://stefanprodan.github.io/podinfo
helm install frontend podinfo/podinfo

apt on Ubuntu compared with Helm on Kubernetes

With Helm you can:

  • install third-party apps and cluster tools such as Prometheus, Grafana, or Argo CD,
  • upgrade them to a new version or new settings,
  • roll back to an earlier version when an upgrade goes wrong,
  • uninstall them with one command,
  • package your own app so other teams can install it the same way.

Installing Prometheus by hand is not one YAML file. It needs a namespace, service accounts, permissions, ConfigMaps, a StatefulSet, and more, in the right combination. Helm turns that into one command with versioning and customisation built in.

3. What a chart is

A chart is an application deployment package. It holds everything Helm needs to create the Kubernetes resources for an app.

webapp/
├── Chart.yaml
├── values.yaml
├── charts/
└── templates/
    ├── deployment.yaml
    └── service.yaml
File or folder What it contains Everyday comparison
Chart.yaml Package information: name, versions, description The label on a product box
values.yaml Default settings The choices on an order form
templates/ Kubernetes YAML with placeholders The reusable form itself
charts/ Other charts this one depends on, if any Extra packages included in the box

Anatomy of a Helm chart: Chart.yaml, values.yaml, and templates, and how values fill the templates

A chart contains deployment instructions, not your source code and not your container image. The image lives in a container registry. The chart only says which image to run:

image:
  repository: mycompany/orderup
  tag: "1.0.0"

Helm writes those values into the Deployment. Kubernetes then pulls the image from the registry.

4. Chart, release, and revision

These three words are easy to mix up.

Term Meaning
Chart The reusable deployment package
Release One named installation of that chart
Revision One entry in a release’s history

Install the webapp chart once:

helm install food-dev ./webapp
  • helm install creates a new installation.
  • food-dev is the release name.
  • ./webapp is where the chart is.

Install the same chart again with a different name:

helm install food-test ./webapp

Now you have two releases, food-dev and food-test, from one chart.

To change food-dev, upgrade it:

helm upgrade food-dev ./webapp

The release keeps its name, and Helm records revision 2. Do not “upgrade” by installing under a new name such as food-dev-v2. That creates a second, separate release.

Chart = package. Release = installation. Revision = installation history.

5. What a repository is

A Helm repository is a place where packaged charts are stored and shared, like the Ubuntu archive for .deb packages.

Three kinds of storage are easy to confuse:

Location Stores
Source-code repository (GitHub, GitLab) App code, chart source files, documentation
Container registry (Docker Hub, GHCR, ECR) Container images
Helm chart repository Packaged charts and an index listing them

Some registries can store both images and charts using OCI support, but they are still different kinds of files.

helm repo add gives a repository a local nickname. It does not install anything. helm repo update refreshes your local copy of the repository’s chart list. It does not upgrade anything already running in the cluster.

How installing with Helm works: add a repo, search it, then install into the cluster

6. How Helm finds your cluster

Helm runs on your machine, not inside the cluster. Since Helm 3 there is no server component to install (Helm 2 needed one called Tiller).

Helm reads the same kubeconfig file as kubectl and uses its current context:

Setting Chooses
Context Which cluster and which user identity
Namespace Which area inside that cluster

Helm uses your identity and permissions. If kubectl is not allowed to create a Deployment, Helm is not allowed either. Always check the context before installing anything.

7. Templates, values, and rendering

A plain Deployment has fixed numbers:

spec:
  replicas: 1

A Helm template has a placeholder instead:

spec:
  replicas: {{ .Values.replicaCount }}

And values.yaml supplies the number:

replicaCount: 3

Helm combines them and produces:

spec:
  replicas: 3

This step is called rendering: replacing template expressions with real values to produce normal Kubernetes YAML. Kubernetes only ever receives the finished YAML. It never sees .Values.

Two built-in objects you will use most:

Object Holds Example
.Values Settings from values.yaml, -f files, and --set {{ .Values.image.tag }}
.Release Facts about this installation {{ .Release.Name }} gives food-dev

Using .Release.Name in resource names lets the same chart be installed many times without name clashes. food-dev-web and food-test-web can live side by side.

A value only changes something if a template uses it. Adding favoriteColor: blue to values.yaml does nothing unless a template refers to .Values.favoriteColor.

8. Chart.yaml and the three kinds of version

apiVersion: v2
name: webapp
description: A small web application chart for learning Helm
type: application
version: 0.1.0
appVersion: "1.28.0"
Field Meaning
apiVersion Chart format. v2 for Helm 3 and later.
name Chart name
type application (installs things) or library (shared helpers only)
version Version of the chart package
appVersion Version of the app it deploys, for information

There are three separate version ideas:

Version Example What it identifies
Chart version 0.1.0 The deployment package
App or image version 1.28.0 The application build
Release revision 2 An entry in the release history

Changing appVersion alone does not change the image that runs. A template must use it, or you must change the image value.

9. Follow one installation from start to finish

Suppose you run:

helm install demo ./webapp -n learning --create-namespace
  1. Helm reads the chart. It loads Chart.yaml, the templates, the default values, and any dependencies.
  2. Helm prepares the values. It merges the chart defaults with your overrides.
  3. Helm renders the templates. Placeholders become real names, replica counts, and images.
  4. Helm sends the YAML to the API server. The API server checks your permissions and validates the resources. Helm stores a record of the release in the namespace.
  5. Kubernetes controllers take over. The Deployment controller creates a ReplicaSet, which creates Pods. The scheduler picks nodes, and each node’s kubelet starts the containers through the container runtime.
  6. The app becomes ready. When Pods pass their readiness checks, the Service sends them traffic.

Helm describes and submits the installation. Kubernetes keeps it running.

10. Install flow and traffic flow are separate

When you deploy, Helm generates resources and sends them to Kubernetes.

When a customer visits the website, the request travels through the network:

Customer → load balancer → Ingress controller → Service → app Pod

Helm is not in that path. It may have created the Ingress and Service, but it is not a proxy between customers and the app. If Helm is uninstalled from your laptop, the app keeps running.

Also, an Ingress resource does nothing on its own. An Ingress controller must be running in the cluster to act on its rules.

11. One chart, many environments

Keep the defaults in the chart’s values.yaml:

replicaCount: 1

Put production differences in values-prod.yaml:

replicaCount: 3

When the same setting comes from several places, Helm uses this priority:

Input Priority
Chart’s values.yaml Lowest
Your -f values files Higher; a later -f beats an earlier one
--set on the command line Highest

So -f values-prod.yaml --set replicaCount=2 gives 2 replicas.

For team work, prefer values files stored in Git over long --set commands. They are easier to review and to repeat.

During helm upgrade, do not assume your earlier overrides are carried forward. Pass the same -f files and --set flags every time, so the result is predictable.

12. Upgrades and rollbacks

An upgrade renders the chart again and applies only the differences. What Kubernetes then does depends on what changed:

Change What usually happens
Replica count Pods are added or removed
Container image The Deployment starts a rolling update
Pod environment variables The Deployment starts a rolling update
Service port The Service changes; Pods are untouched
ConfigMap content The ConfigMap changes; Pods may not restart by themselves

An upgrade does not restart every Pod. Pods that read a ConfigMap as environment variables keep the old values until they are replaced.

A rollback applies the configuration of an earlier revision and records it as a new revision. Rolling back from revision 2 to revision 1 creates revision 3.

Rollback has a limit. If the new version changed a database table or deleted data, rolling back the Deployment does not restore the database.

Helm rollback restores release configuration. It is not a backup.

A release’s life: install, upgrade, rollback, and uninstall, with helm history updating at each step

13. What Helm does not solve for you

Concern Still needs separate work
Security Image scanning, permissions, network rules, secret management
Availability Capacity, node placement, disruption budgets
Data protection Backups, restore tests, database migrations
App correctness Functional and integration testing
Cloud prerequisites IAM roles, networks, load balancers
Drift correction If someone edits a Helm-managed resource by hand, Helm does not undo it. A GitOps tool such as Argo CD can.

Helm is a packaging and release tool, not a whole platform.

Part 2: Commands and practice

You need kubectl and helm connected to a practice cluster, such as kind, minikube, or Rancher Desktop. The commands work with Helm 3 and Helm 4.

1. Check where Helm will install

helm version
kubectl config current-context
kubectl get nodes

If the context is wrong, switch it before going further:

kubectl config use-context <context-name>

You can also choose a context or namespace for a single Helm command:

helm list --kube-context <context-name> -n <namespace>

2. Install a third-party chart

Add the repository once, then refresh it:

helm repo add podinfo https://stefanprodan.github.io/podinfo
helm repo update
helm search repo podinfo

Look at the chart before installing it:

helm show chart podinfo/podinfo
helm show values podinfo/podinfo > podinfo-values.yaml

helm show values lists every setting the chart accepts. Read it before any production install.

Install it with a release name and one override:

helm install frontend podinfo/podinfo \
  -n demo --create-namespace \
  --set replicaCount=2

helm list -n demo
kubectl get pods -n demo

Remove it, using the release name, not the chart name:

helm uninstall frontend -n demo

3. When the chart is not in your repositories

Many projects publish their own repository. Search Artifact Hub, the public chart catalogue, from the command line:

helm search hub argo-cd

Then add the vendor’s repository from its official documentation. For example:

helm repo add argo https://argoproj.github.io/argo-helm
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm search repo argo/argo-cd

Some charts need extra values before they work. The AWS Load Balancer Controller, for example, needs clusterName and IAM permissions. Read helm show values and the vendor’s install guide first.

Bitnami was long the default place to look. In 2025 Bitnami changed how its free images are published, so check where a Bitnami chart’s images come from before you rely on it.

4. Build your own chart

helm create webapp generates a full starter chart. Its files depend on each other, so if you replace values.yaml with two settings and keep the generated templates, rendering fails. For learning, build a small chart by hand:

mkdir -p webapp/templates

webapp/Chart.yaml:

apiVersion: v2
name: webapp
description: A small web application chart for learning Helm
type: application
version: 0.1.0
appVersion: "1.28.0"

webapp/values.yaml:

replicaCount: 1

image:
  repository: nginx
  tag: "1.28.0"

service:
  port: 80

webapp/templates/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-web
  labels:
    app: {{ .Release.Name }}-web
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}-web
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-web
    spec:
      containers:
        - name: web
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
          resources:
            requests:
              cpu: 50m
              memory: 64Mi

webapp/templates/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-web
spec:
  selector:
    app: {{ .Release.Name }}-web
  ports:
    - port: {{ .Values.service.port }}
      targetPort: 80

5. Check the chart before installing

helm lint ./webapp
helm template demo ./webapp -n learning

helm lint finds common chart mistakes. helm template prints the finished YAML without installing anything:

# Source: webapp/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: demo-web
...

Notice {{ .Release.Name }}-web became demo-web.

Command Question it answers
helm show values Which settings does the chart expose?
helm lint Does the chart have common problems?
helm template What YAML will these templates produce?

A correct preview does not prove the app will run. Image pulls, permissions, storage, and capacity still matter.

6. Install and verify

helm install demo ./webapp \
  -n learning --create-namespace \
  --wait --timeout 5m
Option Purpose
-n learning Install into the learning namespace
--create-namespace Create the namespace if it does not exist
--wait Wait until the resources are ready before reporting success
--timeout 5m Give up waiting after five minutes

Check Helm’s view, then Kubernetes’ view:

helm list -n learning
helm status demo -n learning
kubectl get deployments,pods,services -n learning
kubectl rollout status deployment/demo-web -n learning

Finally, test the app itself:

kubectl port-forward -n learning service/demo-web 8080:80

In another terminal:

curl -I http://localhost:8080

You should see HTTP/1.1 200 OK. Helm’s status, Kubernetes readiness, and a real response are three different pieces of evidence. Check all three.

7. Use the same chart for production settings

Create values-prod.yaml:

replicaCount: 3

Install a production-style release from the same chart:

helm install food-prod ./webapp \
  -n production --create-namespace \
  -f values-prod.yaml

Preview how --set beats the file:

helm template food-prod ./webapp -f values-prod.yaml --set replicaCount=2 | grep replicas
  replicas: 2

8. Upgrade, check history, and roll back

helm upgrade demo ./webapp -n learning --set replicaCount=3 --wait
helm history demo -n learning
REVISION  STATUS      DESCRIPTION
1         superseded  Install complete
2         deployed    Upgrade complete

The real output also shows the update time, chart, and app version columns.

Go back to revision 1:

helm rollback demo 1 -n learning --wait
helm history demo -n learning
kubectl get pods -n learning

The history now shows revision 3, which matches revision 1’s configuration. The Deployment is back to one replica.

To install if the release is missing and upgrade if it exists, use one command. This is common in CI pipelines:

helm upgrade --install demo ./webapp -n learning --create-namespace

9. Package and share your chart

Package the chart into one file:

helm package ./webapp -d dist
Successfully packaged chart and saved it to: dist/webapp-0.1.0.tgz

The .tgz holds the chart only, not the container image. You can install it directly:

helm install demo2 ./dist/webapp-0.1.0.tgz -n learning

To share it through a classic HTTP repository, create an index:

helm repo index ./dist --url https://charts.example.com

This writes dist/index.yaml, which lists every chart and where to download it. It does not upload anything. Upload the .tgz files and index.yaml to a web host such as GitHub Pages, Nexus, or Artifactory. Then anyone can run:

helm repo add orderup https://charts.example.com
helm install payments orderup/webapp

https://charts.example.com is a placeholder; use the address where you actually published the files.

Ship your own app: create, customise, package, index, publish, install

OCI registries are the other option. They skip index.yaml and use push and pull:

helm push dist/webapp-0.1.0.tgz oci://registry.example.com/charts
helm install demo oci://registry.example.com/charts/webapp --version 0.1.0

10. Troubleshoot

Work from the release down to the container:

helm status demo -n learning
helm history demo -n learning
helm get values demo -n learning --all
helm get manifest demo -n learning
kubectl get pods -n learning
kubectl describe pod <pod-name> -n learning
kubectl logs <pod-name> -n learning

helm get manifest shows exactly what Helm sent to Kubernetes. helm get values --all shows the final values after every override.

Symptom Where to look
Template error during install Template syntax, indentation, missing values; run helm template
Pending Pods Scheduling, resource requests, storage
ImagePullBackOff Image name, tag, registry credentials
CrashLoopBackOff App startup; read kubectl logs
Service cannot reach Pods Selector labels, readiness, ports
release: not found Release name, namespace, or context
cannot re-use a name that is still in use The release exists; use helm upgrade or pick another name

A failed install can leave resources behind. Look at what exists before you try again.

11. Clean up

helm uninstall demo -n learning
helm uninstall demo2 -n learning
helm uninstall food-prod -n production
kubectl delete namespace learning production demo

Uninstalling does not always delete everything. PersistentVolumeClaims and some custom resources can be left behind on purpose, depending on the chart.

12. Helm cheat sheet

Goal Command
Add a repository helm repo add <name> <url>
Refresh repositories helm repo update
Search added repositories helm search repo <keyword>
Search Artifact Hub helm search hub <keyword>
See chart settings helm show values <repo>/<chart>
Preview YAML helm template <release> <chart>
Check a chart helm lint <chart-dir>
Install helm install <release> <chart> -n <ns> --create-namespace
Override values -f values-prod.yaml or --set key=value
Install or upgrade helm upgrade --install <release> <chart>
List releases helm list -A
Release details helm status <release>
History helm history <release>
Roll back helm rollback <release> <revision>
Uninstall helm uninstall <release>
Create a starter chart helm create <name>
Package helm package <chart-dir>
Build a repository index helm repo index <dir> --url <url>

13. Check your understanding

  • Which command shows the YAML Helm will send, without installing?
  • If you install the same chart twice with different names, how many releases do you have?
  • Why does helm repo update not change your running apps?
  • If values-prod.yaml says 3 replicas and --set says 2, which wins?
  • After rolling back from revision 2 to revision 1, what is the new revision number?
  • Why can rollback not undo a database change?

The roles stay small: A chart is the package. Values customise it. Templates turn values into YAML. A release is one installation, and revisions are its history. A repository shares charts. Kubernetes keeps the app running.

Official references