Deploy Applications on Kubernetes with Helm

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package helm 3.21.3
kubectl 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

INFO
CKAD includes using Helm to deploy existing packages. Part 1 covers that core workflow, then adds upgrade, rollback, history, and uninstall as practical Helm release-management skills. Part 2 below covers chart creation for practical depth beyond the exam-focused slice.

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):

bash
mkdir -p "$HOME/.local/bin"
bash
curl -fsSL -o get-helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
bash
chmod 700 get-helm.sh
bash
DESIRED_VERSION=v3.21.3 HELM_INSTALL_DIR="$HOME/.local/bin" USE_SUDO=false ./get-helm.sh

The 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:

bash
helm version

Sample output:

output
version.BuildInfo{Version:"v3.21.3", GitCommit:"1ad6e68924fdf6fb0c7dcef8e9e1dfc0f36eaed6", GitTreeState:"clean", GoVersion:"go1.26.5"}

Helm stores configuration under your home directory:

bash
helm env

Sample output:

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:

bash
helm repo add bitnami https://charts.bitnami.com/bitnami

Sample output:

output
"bitnami" has been added to your repositories

List configured repositories:

bash
helm repo list

Sample output:

output
NAME   	URL                               
bitnami	https://charts.bitnami.com/bitnami

Refresh chart index metadata before install or upgrade:

bash
helm repo update

Sample output:

output
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

Search charts in configured repositories:

bash
helm search repo bitnami/nginx

Sample output:

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:

bash
helm search hub nginx

Sample output:

output
URL                                               	CHART VERSION  	APP VERSION
https://artifacthub.io/packages/helm/bitnami/nginx	25.0.15        	1.31.3

Inspect chart metadata before install:

bash
helm show chart bitnami/nginx --version 25.0.15

Sample output (trimmed):

output
apiVersion: v2
name: nginx
version: 25.0.15
appVersion: 1.31.3
bash
helm show values bitnami/nginx --version 25.0.15

Sample output (trimmed):

output
## @section Global parameters
replicaCount: 1
service:
  type: LoadBalancer

The values output lists configurable keys such as replicaCount and service.type. Pin --version when you need a specific chart revision.

bash
helm show readme bitnami/nginx --version 25.0.15

Readme text summarizes chart purpose and post-install notes.

Install and inspect a release

Create the lab namespace:

bash
kubectl create namespace helm-lab

Sample output:

output
namespace/helm-lab created

Install bitnami/nginx as release web. This becomes revision 1:

bash
helm install web bitnami/nginx -n helm-lab --version 25.0.15 --set replicaCount=2 --set service.type=ClusterIP --wait --timeout=5m

Sample output (trimmed):

output
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:

bash
helm list -n helm-lab

Sample output:

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.3

The APP VERSION column comes from chart metadata (appVersion). It does not prove the exact container image running in each Pod.

bash
helm status web -n helm-lab

Sample output (trimmed):

output
NAME: web
NAMESPACE: helm-lab
STATUS: deployed
REVISION: 1

Inspect Kubernetes objects Helm created:

bash
kubectl get deploy,svc -n helm-lab -l app.kubernetes.io/instance=web

Sample output:

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   29s

The 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:

bash
helm install demo bitnami/nginx -n helm-demo --create-namespace --version 25.0.15 --set service.type=ClusterIP --wait --timeout=5m

helm upgrade --install combines install and upgrade in one command. Use it when a release may or may not already exist:

bash
helm upgrade --install demo bitnami/nginx -n helm-demo --version 25.0.15 --set replicaCount=1 --set service.type=ClusterIP --wait --timeout=5m

Remove the throwaway release before continuing the main walkthrough:

bash
helm uninstall demo -n helm-demo --wait
bash
kubectl delete namespace helm-demo

Customize with --set and values files

Pass overrides inline with --set at install or upgrade time:

bash
helm install RELEASE CHART -n NAMESPACE --set replicaCount=2 --set service.type=ClusterIP

Revision 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:

yaml
replicaCount: 3

service:
  type: ClusterIP

The 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:

bash
helm upgrade web bitnami/nginx -n helm-lab --version 25.0.15 -f nginx-values.yaml --wait --timeout=5m

Sample output (trimmed):

output
Release "web" has been upgraded. Happy Helming!
REVISION: 2
STATUS: deployed

Each successful upgrade adds a new revision in Helm history.

Confirm the merged user values Helm recorded:

bash
helm get values web -n helm-lab

Sample output:

output
USER-SUPPLIED VALUES:
replicaCount: 3
service:
  type: ClusterIP

Verify the Deployment now requests three replicas:

bash
kubectl get deploy web-nginx -n helm-lab -o jsonpath='{.spec.replicas}{"\n"}'

Sample output:

output
3

List release history before rollback:

bash
helm history web -n helm-lab

Sample output:

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 complete
bash
helm get manifest web -n helm-lab

Sample output (trimmed):

output
---
# 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.15

helm get manifest prints the rendered YAML Helm applied for the current revision.

Roll back to revision 1. This creates revision 3:

bash
helm rollback web 1 -n helm-lab --wait --timeout=5m

Sample output:

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.

bash
helm history web -n helm-lab

Sample output:

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 1

Verify replicas returned to two:

bash
kubectl get deploy web-nginx -n helm-lab -o jsonpath='{.spec.replicas}{"\n"}'

Sample output:

output
2

Helm 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:

bash
helm uninstall web -n helm-lab --wait

Sample output:

output
release "web" uninstalled
bash
helm list -n helm-lab

Sample output:

output
NAME	NAMESPACE	REVISION	UPDATED	STATUS	CHART	APP VERSION

An 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

INFO
This section explains how charts are created and templated. It extends beyond the core CKAD Helm workflow in Part 1.

Generate and simplify the chart structure

Generate a starter chart:

bash
helm create demo-chart

Sample output:

output
Creating demo-chart

Sample directory layout:

text
demo-chart/
  Chart.yaml
  values.yaml
  charts/
  templates/
    deployment.yaml
    service.yaml
    ingress.yaml
    NOTES.txt
    _helpers.tpl

Keep _helpers.tpl, but remove manifests the minimal example will not use:

bash
find demo-chart/templates -type f ! -name '_helpers.tpl' -delete

Configure Chart.yaml and values.yaml

Save this metadata as demo-chart/Chart.yaml:

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:

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:

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:

bash
helm lint demo-chart

Sample output:

output
==> Linting demo-chart
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed

Render templates locally without contacting the cluster:

bash
helm template web-demo demo-chart

Sample output (trimmed):

output
---
# Source: demo-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-demo-demo-chart
spec:
  replicas: 1

Dry-run modes differ:

  • helm template — render locally
  • helm install --dry-run=client --debug — simulate client-side
  • helm install --dry-run=server --debug — simulate with cluster connectivity
bash
helm install web-demo demo-chart -n helm-lab --dry-run=client --debug

Sample output (trimmed):

output
NAME: web-demo
NAMESPACE: helm-lab
STATUS: pending-install
REVISION: 1
bash
helm install web-demo demo-chart -n helm-lab --dry-run=server --debug

Sample output (trimmed):

output
NAME: web-demo
NAMESPACE: helm-lab
STATUS: pending-install
REVISION: 1

helm 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:

bash
helm install web-demo demo-chart -n helm-lab --wait --timeout=5m

Sample output (trimmed):

output
NAME: web-demo
NAMESPACE: helm-lab
STATUS: deployed
REVISION: 1

Verify the rendered Deployment exists:

bash
kubectl get deployment -n helm-lab -l app.kubernetes.io/instance=web-demo

Sample output:

output
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
web-demo-demo-chart   1/1     1            1           16s

Package for distribution:

bash
helm package demo-chart

Sample output:

output
Successfully packaged chart and saved it to: demo-chart-0.1.0.tgz

Upgrade the packaged chart with a values override:

bash
helm upgrade web-demo demo-chart-0.1.0.tgz -n helm-lab --set replicaCount=2 --wait --timeout=5m

Sample output (trimmed):

output
Release "web-demo" has been upgraded. Happy Helming!
REVISION: 2
STATUS: deployed

Confirm the Deployment now requests two replicas:

bash
kubectl get deployment -n helm-lab -l app.kubernetes.io/instance=web-demo -o jsonpath='{.items[0].spec.replicas}{"\n"}'

Sample output:

output
2

Hooks and named templates

Brief pointers only — full examples live in dedicated articles:


What's Next


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.


Frequently Asked Questions

1. What is the difference between a Helm chart and a release?

A chart is the packaged application templates and defaults. A release is one installed instance of that chart in a cluster namespace, identified by the release name you choose at install time.

2. Should I use --set or a values file?

Use --set for one or two quick overrides on the command line. Use a values file when you have several settings, want reusable configuration, or need structured YAML for nested keys.

3. How do I roll back a Helm release?

Run helm history to see revision numbers, then helm rollback RELEASE REVISION. Helm restores the chart templates and values from that revision; it is separate from kubectl rollout undo on a Deployment.

4. What is the difference between helm search repo and helm search hub?

helm search repo searches charts in repositories you added with helm repo add. helm search hub searches Artifact Hub metadata across many public chart sources without adding each repo locally.

5. Does helm uninstall delete PersistentVolumeClaims?

helm uninstall removes release-owned resources and release history by default. Resources annotated with helm.sh/resource-policy: keep, hook-created resources without an applicable hook deletion policy, and storage retained by Kubernetes or the chart can remain.

6. Is creating charts required for CKAD?

CKAD includes using Helm to deploy existing packages. Installing, upgrading, and rolling back charts with values and release commands is practical Helm knowledge beyond the core exam objective. Building charts from scratch extends further into Part 2.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)