| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | helm 3.21.3kubectl 1.36.3 |
| Applies to | Any host with kubectl configured; any Kubernetes cluster |
| Cert prep | CKAD · CKA |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user for Helm and kubectl operations; install Helm to $HOME/.local/bin without sudo |
| Scope | Part 1 — install Helm, repositories, search, install, values and --set, upgrade, history, rollback, uninstall, and deployment troubleshooting pointers. Part 2 — helm create, Chart.yaml, values.yaml, basic templates, lint, template, package. Does not cover hooks in depth, subcharts, OCI publishing, chart signing, Helmfile, or GitOps. |
| Related guides | Kubernetes Services |
This tutorial uses the helm-lab namespace and the bitnami/nginx chart pinned to chart version 25.0.15 so the chart structure, values, and release-history examples remain consistent. The chart currently uses the rolling bitnami/nginx:latest image, so Pod image contents can change over time. Part 1 covers the core Helm consumer workflow. Part 2 introduces chart authoring.
What Is Helm?
Helm is a package manager for Kubernetes. A chart bundles templates and default configuration for Kubernetes resources. Installing a chart creates a release. The same chart can be installed many times with different release names or values.
| Term | Meaning |
|---|---|
| Chart | Packaged Kubernetes application |
| Repository | Source containing published charts |
| Release | Installed instance of a chart |
| Values | Configuration supplied to chart templates |
Part 1 — Deploy Existing Charts with Helm
Install Helm 3.21.3
This course is tested with Helm 3.21.3. Helm 4 is the current stable major release, while Helm 3 remains in support mode.
Install Helm into a user-owned directory so you do not need elevated permission on the workstation. Download the upstream installer with the curl command (-fsSL follows redirects and fails on HTTP errors):
mkdir -p "$HOME/.local/bin"curl -fsSL -o get-helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3chmod 700 get-helm.shDESIRED_VERSION=v3.21.3 HELM_INSTALL_DIR="$HOME/.local/bin" USE_SUDO=false ./get-helm.shThe installer supports DESIRED_VERSION; without it, the installed version and helm version output can change.
Ensure $HOME/.local/bin is in your PATH.
Confirm the installed version:
helm versionSample output:
version.BuildInfo{Version:"v3.21.3", GitCommit:"1ad6e68924fdf6fb0c7dcef8e9e1dfc0f36eaed6", GitTreeState:"clean", GoVersion:"go1.26.5"}Helm stores configuration under your home directory:
helm envSample output:
HELM_BIN="helm"
HELM_CACHE_HOME="/home/labuser/.cache/helm"
HELM_CONFIG_HOME="/home/labuser/.config/helm"
HELM_DATA_HOME="/home/labuser/.local/share/helm"
HELM_KUBECONTEXT=""
HELM_NAMESPACE="default"Helm uses your kubeconfig the same way kubectl does. Windows and macOS installers are available from the Helm documentation; this lab uses Linux only.
Add, update, search, and inspect repositories
Add the Bitnami repository used throughout Part 1:
helm repo add bitnami https://charts.bitnami.com/bitnamiSample output:
"bitnami" has been added to your repositoriesList configured repositories:
helm repo listSample output:
NAME URL
bitnami https://charts.bitnami.com/bitnamiRefresh chart index metadata before install or upgrade:
helm repo updateSample output:
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈Search charts in configured repositories:
helm search repo bitnami/nginxSample output:
NAME CHART VERSION APP VERSION DESCRIPTION
bitnami/nginx 25.0.15 1.31.3 NGINX Open Source is a web server that can be a...Search Artifact Hub without adding every publisher repo:
helm search hub nginxSample output:
URL CHART VERSION APP VERSION
https://artifacthub.io/packages/helm/bitnami/nginx 25.0.15 1.31.3Inspect chart metadata before install:
helm show chart bitnami/nginx --version 25.0.15Sample output (trimmed):
apiVersion: v2
name: nginx
version: 25.0.15
appVersion: 1.31.3helm show values bitnami/nginx --version 25.0.15Sample output (trimmed):
## @section Global parameters
replicaCount: 1
service:
type: LoadBalancerThe values output lists configurable keys such as replicaCount and service.type. Pin --version when you need a specific chart revision.
helm show readme bitnami/nginx --version 25.0.15Readme text summarizes chart purpose and post-install notes.
Install and inspect a release
Create the lab namespace:
kubectl create namespace helm-labSample output:
namespace/helm-lab createdInstall bitnami/nginx as release web. This becomes revision 1:
helm install web bitnami/nginx -n helm-lab --version 25.0.15 --set replicaCount=2 --set service.type=ClusterIP --wait --timeout=5mSample output (trimmed):
NAME: web
NAMESPACE: helm-lab
STATUS: deployed
REVISION: 1--wait blocks until the chart resources become ready. Without it, STATUS: deployed only means Helm submitted the manifests.
List releases in the namespace:
helm list -n helm-labSample output:
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
web helm-lab 1 2026-07-26 17:20:37.118754569 +0530 IST deployed nginx-25.0.15 1.31.3The APP VERSION column comes from chart metadata (appVersion). It does not prove the exact container image running in each Pod.
helm status web -n helm-labSample output (trimmed):
NAME: web
NAMESPACE: helm-lab
STATUS: deployed
REVISION: 1Inspect Kubernetes objects Helm created:
kubectl get deploy,svc -n helm-lab -l app.kubernetes.io/instance=webSample output:
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web-nginx 2/2 2 2 28s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/web-nginx ClusterIP 10.99.122.232 <none> 80/TCP,443/TCP 29sThe Bitnami nginx chart defaults service.type to LoadBalancer. This lab sets ClusterIP so a kubeadm cluster does not depend on a cloud load-balancer implementation.
Install into a new namespace in one step when you need a throwaway release:
helm install demo bitnami/nginx -n helm-demo --create-namespace --version 25.0.15 --set service.type=ClusterIP --wait --timeout=5mhelm upgrade --install combines install and upgrade in one command. Use it when a release may or may not already exist:
helm upgrade --install demo bitnami/nginx -n helm-demo --version 25.0.15 --set replicaCount=1 --set service.type=ClusterIP --wait --timeout=5mRemove the throwaway release before continuing the main walkthrough:
helm uninstall demo -n helm-demo --waitkubectl delete namespace helm-demoCustomize with --set and values files
Pass overrides inline with --set at install or upgrade time:
helm install RELEASE CHART -n NAMESPACE --set replicaCount=2 --set service.type=ClusterIPRevision 1 in this lab already used --set for replicaCount and service.type. For a quick one-off change on an existing release, the same flag works on helm upgrade.
--set suits one or two quick changes. For reusable configuration, use a values file.
Save overrides as nginx-values.yaml:
replicaCount: 3
service:
type: ClusterIPThe file merges with chart defaults. Nested keys use YAML indentation under parent keys such as service.
| Method | Best suited for |
|---|---|
--set |
Small command-line overrides |
| Values file | Multiple or reusable settings |
Use --set-string when a value must stay a string (for example a numeric-looking tag that should not be coerced).
Upgrade, inspect history, and roll back
Apply the values file to the existing web release. This becomes revision 2:
helm upgrade web bitnami/nginx -n helm-lab --version 25.0.15 -f nginx-values.yaml --wait --timeout=5mSample output (trimmed):
Release "web" has been upgraded. Happy Helming!
REVISION: 2
STATUS: deployedEach successful upgrade adds a new revision in Helm history.
Confirm the merged user values Helm recorded:
helm get values web -n helm-labSample output:
USER-SUPPLIED VALUES:
replicaCount: 3
service:
type: ClusterIPVerify the Deployment now requests three replicas:
kubectl get deploy web-nginx -n helm-lab -o jsonpath='{.spec.replicas}{"\n"}'Sample output:
3List release history before rollback:
helm history web -n helm-labSample output:
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Sun Jul 26 17:20:37 2026 superseded nginx-25.0.15 1.31.3 Install complete
2 Sun Jul 26 17:21:14 2026 deployed nginx-25.0.15 1.31.3 Upgrade completehelm get manifest web -n helm-labSample output (trimmed):
---
# Source: nginx/templates/networkpolicy.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: web-nginx
namespace: "helm-lab"
labels:
app.kubernetes.io/instance: web
helm.sh/chart: nginx-25.0.15helm get manifest prints the rendered YAML Helm applied for the current revision.
Roll back to revision 1. This creates revision 3:
helm rollback web 1 -n helm-lab --wait --timeout=5mSample output:
Rollback was a success! Happy Helming!A rollback creates a new release revision. It does not make the selected old revision current in place. Helm restores the chart templates and values from the target revision while keeping earlier entries in history.
helm history web -n helm-labSample output:
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Sun Jul 26 17:20:37 2026 superseded nginx-25.0.15 1.31.3 Install complete
2 Sun Jul 26 17:21:14 2026 superseded nginx-25.0.15 1.31.3 Upgrade complete
3 Sun Jul 26 17:21:26 2026 deployed nginx-25.0.15 1.31.3 Rollback to 1Verify replicas returned to two:
kubectl get deploy web-nginx -n helm-lab -o jsonpath='{.spec.replicas}{"\n"}'Sample output:
2Helm rollback restores prior release configuration. That is separate from Deployment rolling updates and rollbacks performed with kubectl rollout undo on a single Deployment object.
Uninstall and troubleshoot a release
Remove the web release before Part 2:
helm uninstall web -n helm-lab --waitSample output:
release "web" uninstalledhelm list -n helm-labSample output:
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSIONAn empty table means no releases remain in that namespace.
| Symptom | Likely cause | Fix |
|---|---|---|
| Chart or repo not found | Stale index or wrong name | Run helm repo update; verify repo and chart names |
| Release name already exists | Prior install in namespace | Use another release name or helm upgrade the existing release |
| Namespace missing | Target namespace not created | Use --create-namespace or create the namespace first |
| Custom values ignored | Wrong key path or YAML indentation | Compare with helm show values; verify with helm get values |
| Rollback confusion | Mixing Helm and Deployment rollback | Use helm rollback for release revisions; use kubectl rollout undo only for Deployment template history |
Part 2 — Create a Basic Helm Chart
Generate and simplify the chart structure
Generate a starter chart:
helm create demo-chartSample output:
Creating demo-chartSample directory layout:
demo-chart/
Chart.yaml
values.yaml
charts/
templates/
deployment.yaml
service.yaml
ingress.yaml
NOTES.txt
_helpers.tplKeep _helpers.tpl, but remove manifests the minimal example will not use:
find demo-chart/templates -type f ! -name '_helpers.tpl' -deleteConfigure Chart.yaml and values.yaml
Save this metadata as demo-chart/Chart.yaml:
apiVersion: v2
name: demo-chart
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"version— chart package version (changes when you change templates or defaults)appVersion— application version label for humans and metadata (not the chart revision itself)
Save these defaults as demo-chart/values.yaml:
replicaCount: 1
image:
repository: nginx
tag: ""The Deployment template reads these settings through .Values.replicaCount, .Values.image.repository, and .Values.image.tag.
Create a valid Deployment template
Save this complete Deployment as demo-chart/templates/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "demo-chart.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "demo-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "demo-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "demo-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"The selector and template labels must remain identical. {{ .Release.Name }} is the install-time release name. {{ .Chart.Name }} comes from Chart.yaml.
Lint, render, and dry-run
Check chart structure and common issues:
helm lint demo-chartSample output:
==> Linting demo-chart
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failedRender templates locally without contacting the cluster:
helm template web-demo demo-chartSample output (trimmed):
---
# Source: demo-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo-demo-chart
spec:
replicas: 1Dry-run modes differ:
helm template— render locallyhelm install --dry-run=client --debug— simulate client-sidehelm install --dry-run=server --debug— simulate with cluster connectivity
helm install web-demo demo-chart -n helm-lab --dry-run=client --debugSample output (trimmed):
NAME: web-demo
NAMESPACE: helm-lab
STATUS: pending-install
REVISION: 1helm install web-demo demo-chart -n helm-lab --dry-run=server --debugSample output (trimmed):
NAME: web-demo
NAMESPACE: helm-lab
STATUS: pending-install
REVISION: 1helm lint checks chart structure and common issues. helm template verifies rendering. A server dry-run adds API-server interaction, but a real installation can still fail because of runtime conditions.
Package, install, and upgrade the chart
Install from the chart directory:
helm install web-demo demo-chart -n helm-lab --wait --timeout=5mSample output (trimmed):
NAME: web-demo
NAMESPACE: helm-lab
STATUS: deployed
REVISION: 1Verify the rendered Deployment exists:
kubectl get deployment -n helm-lab -l app.kubernetes.io/instance=web-demoSample output:
NAME READY UP-TO-DATE AVAILABLE AGE
web-demo-demo-chart 1/1 1 1 16sPackage for distribution:
helm package demo-chartSample output:
Successfully packaged chart and saved it to: demo-chart-0.1.0.tgzUpgrade the packaged chart with a values override:
helm upgrade web-demo demo-chart-0.1.0.tgz -n helm-lab --set replicaCount=2 --wait --timeout=5mSample output (trimmed):
Release "web-demo" has been upgraded. Happy Helming!
REVISION: 2
STATUS: deployedConfirm the Deployment now requests two replicas:
kubectl get deployment -n helm-lab -l app.kubernetes.io/instance=web-demo -o jsonpath='{.items[0].spec.replicas}{"\n"}'Sample output:
2Hooks and named templates
Brief pointers only — full examples live in dedicated articles:
- Helm hooks examples —
pre-install,post-install,pre-upgrade, andpost-upgradelifecycle jobs - Helm named templates —
_helpers.tpl,define,include, and shared labels
What's Next
- Kubernetes Bases, Overlays and Patches
- Kubernetes Pods and Pod Lifecycle
- Kubernetes Deployments, Rolling Updates and Rollbacks
References
Summary
Part 1 walked through the core Helm consumer workflow: install Helm 3.21.3 into a user-owned directory, add the Bitnami repository, search and inspect charts, install release web at revision 1 with ClusterIP and two replicas, upgrade to revision 2 with a values file, inspect history and rendered manifests, then roll back to revision 1 as revision 3 and verify replicas returned to two. CKAD includes deploying existing packages with Helm; upgrade, rollback, history, and uninstall are practical release-management skills beyond that core objective.
--wait blocks until chart resources become ready, which makes the lab steps easier to follow in sequence. Helm release rollback creates a new revision and restores prior release configuration — not the same command path as Deployment rollout undo.
Part 2 generated demo-chart, removed unused templates, saved valid Chart.yaml, values.yaml, and Deployment manifests, then used helm lint, helm template, client and server dry-runs, install, package, and upgrade to prove the chart rendered and scaled correctly.

