Helm: chart, release, and the first install
What a chart is made of, what Helm 3 stores in the cluster after install, and how to configure a release with --set.
Kustomize layers edits on top of finished manifests. Helm comes from the other side: its manifests are templates with substitutions, and the specific values are supplied separately. One command deploys the whole application.
Why this matters
An application in Kubernetes rarely consists of one file: a Deployment, a Service, a ServiceAccount, sometimes an Ingress and a ConfigMap. Installing it into three environments with a different replica count and different tags by hand is awkward. Helm packages these manifests together, and pulls out everything that differs between installs into values. Ready-made applications are distributed the same way: databases, ingress controllers, and monitoring get installed from someone else's charts.
Which Helm
Two branches are current: Helm 3 and Helm 4 (released November 2025). This lesson's commands work the same in both.
If you run into an article about Tiller, that's Helm 2. There, a server component called Tiller with broad permissions lived in the cluster. With Helm 3 it's gone: the client talks to the API server directly, under the same permissions as your kubectl, and reads the same kubeconfig.
Chart and release
A chart is a package for Kubernetes: a folder with manifest templates, default values, and metadata. The minimal set of files inside:
myapp/
Chart.yaml # chart name, chart version, app version
values.yaml # default values
templates/ # manifest templates with substitutions
charts/ # nested dependency charts
.helmignore
A release is an installed instance of a chart in a specific namespace. One chart can be installed as many times as you want under different release names, and those will be independent installs.
Where charts come from
The usual path: a chart repository. You add it once, update the index, and search for the chart you need:
helm repo add podinfo https://stefanprodan.github.io/podinfo
helm repo update
helm search repo podinfo
NAME CHART VERSION APP VERSION DESCRIPTION
podinfo/podinfo 6.15.0 6.15.0 Podinfo Helm chart for Kubernetes
Second path: a local folder. A chart skeleton is generated with one command:
helm create myapp
helm create puts a working default chart into the myapp folder:
templates for a Deployment, Service, ServiceAccount, Ingress, HTTPRoute,
HorizontalPodAutoscaler, a test Pod, and a values.yaml with settings.
No network is needed for this, so it's a convenient option for studying
how a chart is put together.
Installing a release
helm install web ./myapp -n demo --create-namespace
web here is the release name, ./myapp is the path to the chart,
-n demo is the namespace. Without --create-namespace, Helm won't
create the namespace itself and will fail if it doesn't exist.
See what you got:
helm list -n demo
helm list -A
helm status web -n demo
Under the hood: ordinary objects
Helm renders templates into ordinary manifests and applies them through
the same API. No separate entity for the application shows up in the
cluster, everything is visible through plain kubectl. A chart from
helm create puts a label with the release name on the objects:
kubectl get deploy,svc -n demo -l app.kubernetes.io/instance=web
Helm keeps track of the release itself. It writes the state of each
revision into a Secret in the same namespace, named like
sh.helm.release.v1.web.v1, where web is the release name and the last
v1 is the revision number. That's where history, rollbacks, and the
answer to which objects belong to the release come from. That's why you
remove a release with helm uninstall, not kubectl delete: otherwise
the Secret stays behind and Helm keeps thinking the release is installed.
The exact list of applied manifests is shown by:
helm get manifest web -n demo
Configuring through values
You don't touch the templates. You change the values the templates rely on. For example, with a flag right in the command:
helm install web ./myapp -n demo \
--set replicaCount=3 \
--set image.repository=nginx \
--set image.tag=1.27.3-alpine
A dot in --set means nesting: image.repository lands in
image: {repository: ...}. What values a chart accepts at all:
helm show values ./myapp
--set guesses the type of a value. true and false become booleans,
an integer like 1042 becomes a number, and 1.25 or
1.27.3-alpine stay strings. A number in a field where Kubernetes
expects a string (a label value, an environment variable) is rejected by
the API. For such values there's --set-string: it passes everything
after = as a string.
Check the result before installing:
helm template web ./myapp --set replicaCount=3
helm install web ./myapp -n demo --dry-run=server
helm template renders locally and doesn't ask the cluster anything.
helm install --dry-run=server reaches out to the cluster, but creates
nothing in it. Write the flag with a value: plain --dry-run in Helm 4
means client and prints a warning that the form without a value is
deprecated.
What usually breaks
A release name is unique within a namespace. Running helm install web
again in the same namespace gives an error about the name already being
taken. The same name in a different namespace gets taken without
complaint.
A forgotten -n puts the release into the namespace from the current
kubeconfig context, not the one you had in mind. Then helm list in the
namespace you wanted shows nothing, and it looks like the install didn't
go through. helm list -A shows where the release actually ended up.
A comma in --set separates value pairs, and a dot means nesting. If you
need a comma or a dot inside the value itself, escape them with a
backslash: --set 'annotations.example\.com/id=a\,b'.
A failed install still leaves the release behind, in status failed. A
repeat helm install with the same name runs into the taken name; you
need helm uninstall first.
A manual edit of objects created by Helm through kubectl edit isn't
reflected in the release state. The next helm upgrade rebuilds the
manifests from the chart and values and brings everything back to how it
was.
What you'll need for the tasks
helm show values <chart>: what values a chart accepts.helm install <release> <chart> -n <ns> --create-namespaceand--set,--set-string.helm list -A,helm status,helm get manifest,helm get values.helm uninstall <release> -n <ns>.kubectl get secrets -n <ns>: where the release record lives.- For both labs:
kubectl config current-contextbefore any install.
Homework
Practice the theory on your own cluster: 1 step with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.
./run.sh start k8s-helm-install # set up the starting state
./run.sh verify k8s-helm-install # check the current step
In short
- With Helm 3 there's no Tiller: the client works with the API server under your kubeconfig's permissions. Commands are the same in Helm 3 and 4.
- A chart is a package of templates and values, a release is an installed instance of a chart in a namespace.
helm install <release> <chart> -n <ns>installs a release; the namespace is created only with--create-namespace.- Helm applies ordinary Deployments and Services, and stores release
state in the Secret
sh.helm.release.v1.<release>.v<revision>. --setsets individual values and turns integers andtrue/falseinto YAML types;--set-stringleaves a string.helm templateandhelm install --dry-run=servershow the resulting manifests before the install.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson k8s-packaging-07-helm-install
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress