Warrior, we've reached one of the most important tools you'll encounter in real Kubernetes projects:
Helm — the package manager for Kubernetes.
So far, we've been writing Kubernetes YAML manually.
That works for learning.
But imagine your production application has:
You could easily end up with dozens of YAML files.
Helm helps turn all of that into a reusable, configurable package.
Imagine your Node.js application has this Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-api
spec:
replicas: 3
template:
spec:
containers:
- name: node-api
image: node-api:1.0.0
ports:
- containerPort: 3000Now you need:
Development
Staging
ProductionDevelopment:
replicas: 1
image: node-api:1.0.0Staging:
replicas: 2
image: node-api:1.2.0Production:
replicas: 10
image: node-api:2.0.0Without templating, you might maintain separate YAML files:
deployment-dev.yaml
deployment-staging.yaml
deployment-prod.yamlNow imagine 20 Kubernetes resources.
This becomes difficult to maintain.
Instead of:
YAML
YAML
YAML
YAML
YAMLyou create:
Helm Chart
│
├── Templates
├── Configuration
└── MetadataThen:
values.yaml
↓
Helm templates
↓
Kubernetes YAML
↓
Kubernetes APIA simple analogy:
apt install nginxnpm install expresspip install djangohelm installHelm packages Kubernetes resources into a reusable unit called a:
Chart
A Helm Chart is a directory containing:
templates
configuration
metadataExample:
node-api-chart/
│
├── Chart.yaml
├── values.yaml
│
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
└── _helpers.tplFirst verify Helm:
helm versionCreate a chart:
helm create node-apiHelm generates a structure similar to:
node-api/
├── Chart.yaml
├── values.yaml
├── charts/
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ ├── serviceaccount.yaml
│ ├── _helpers.tpl
│ └── tests/
└── .helmignoreThe generated chart contains a lot of examples.
For learning, we'll simplify it.
Chart.yaml#This file contains chart metadata.
Example:
apiVersion: v2
name: node-api
description: Helm chart for Node.js API
type: application
version: 1.0.0
appVersion: "2.1.0"Important fields:
| Field | Meaning |
|---|---|
apiVersion |
Helm chart API version |
name |
Chart name |
description |
Description |
type |
application/library |
version |
Chart version |
appVersion |
Application version |
This confuses beginners.
Suppose:
version: 1.3.0
appVersion: "2.5.0"They mean different things.
1.3.0means:
Version of the Helm packaging/templates.
2.5.0means:
Version of the application being deployed.
They don't have to be identical.
values.yaml#This is one of the most important Helm files.
Example:
replicaCount: 3
image:
repository: myregistry/node-api
tag: "2.1.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
containerPort: 3000
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"Think:
values.yaml= configuration.
Now you create:
templates/deployment.yamlInstead of hardcoding:
replicas: 3you write:
replicas: {{ .Values.replicaCount }}Instead of:
image: myregistry/node-api:2.1.0you can write:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"Helm substitutes the values when rendering the chart.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "node-api.fullname" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "node-api.name" . }}
template:
metadata:
labels:
app: {{ include "node-api.name" . }}
spec:
containers:
- name: node-api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.containerPort }}
resources:
{{- toYaml .Values.resources | nindent 12 }}Now the same template can deploy different configurations.
Suppose:
replicaCount: 5
image:
repository: myregistry/node-api
tag: "3.0.0"
containerPort: 3000Helm renders:
replicas: 5
image: myregistry/node-api:3.0.0
containerPort: 3000Then Kubernetes receives the rendered manifests.
Conceptually:
values.yaml
↓
Helm Template Engine
↓
Rendered YAML
↓
Kubernetes APIThis is important.
Helm doesn't run Pods.
Helm doesn't replace Kubernetes.
Instead:
Helm
↓
Generates/manages Kubernetes manifests/releases
↓
Kubernetes
↓
Runs applicationThink:
Helm = packaging + templating + release management
Kubernetes = orchestrationSuppose:
node-api/is your chart.
Install:
helm install node-api ./node-apiHere:
node-apiis the release name.
And:
./node-apiis the chart.
A release is a deployed instance of a Helm chart.
Imagine:
Chart:
node-apiYou could install it multiple times:
node-api-dev
node-api-staging
node-api-prodEach is a separate Helm release.
node-api Chart
│
┌───────────┼───────────┐
↓ ↓ ↓
Dev Staging Prod
Release Release Releasehelm listExample:
NAME NAMESPACE STATUS
node-api default deployed
frontend default deployedAcross namespaces:
helm list -Ahelm install node-api ./node-api \
--namespace production \
--create-namespaceNow:
Release:
node-api
Namespace:
productionSuppose your application changes from:
2.1.0to:
2.2.0Update:
image:
tag: "2.2.0"Then:
helm upgrade node-api ./node-apiHelm updates the release.
You can override values from the command line.
For example:
helm upgrade node-api ./node-api \
--set image.tag=2.2.0This means:
values.yaml
↓
override image.tag
↓
2.2.0This is where Helm becomes really useful.
You could have:
values.yaml
values-dev.yaml
values-staging.yaml
values-prod.yamlExample:
values-dev.yaml#replicaCount: 1
image:
tag: "2.2.0"
resources:
requests:
cpu: "100m"
memory: "128Mi"values-prod.yaml#replicaCount: 5
image:
tag: "2.2.0"
resources:
requests:
cpu: "500m"
memory: "512Mi"Then:
helm install node-api ./node-api \
-f values-dev.yamlProduction:
helm install node-api ./node-api \
-f values-prod.yamlYou can layer values:
helm upgrade node-api ./node-api \
-f values.yaml \
-f values-prod.yamlLater values generally override earlier values for overlapping keys.
Conceptually:
values.yaml
↓
values-prod.yaml
↓
Final configuration--set#You can also override individual values:
helm upgrade node-api ./node-api \
-f values-prod.yaml \
--set image.tag=2.3.0This is useful for CI/CD pipelines.
One of the most useful Helm commands:
helm template node-api ./node-apiThis renders the templates locally.
It does not install the chart.
Think:
Chart
↓
helm template
↓
Generated Kubernetes YAMLThis is extremely useful for debugging templates.
You can use:
helm template node-api ./node-api --debugIf your template has:
replicas: {{ .Values.replicaCount }}you can verify the actual generated YAML before deploying.
Another useful command:
helm install node-api ./node-api --dry-runThis lets you inspect what Helm would attempt to install without actually performing the deployment.
For upgrade:
helm upgrade node-api ./node-api --dry-runExact output can include rendered manifests and validation information depending on the command/options.
This is another powerful feature.
Suppose:
Release revision 1 → working
Release revision 2 → working
Release revision 3 → brokenCheck:
helm history node-apiExample:
REVISION STATUS
1 superseded
2 superseded
3 deployedRollback:
helm rollback node-api 2Now Helm attempts to restore revision 2.
helm history node-apiYou may see:
REVISION UPDATED STATUS
1 ... superseded
2 ... superseded
3 ... deployedThis gives you release history.
kubectl rollout undo#These are related but not identical.
kubectl rollout undo deployment/node-apiWorks at the Kubernetes Deployment level.
helm rollback node-api 2Rolls back the Helm release configuration/revision.
If your Helm chart manages a Deployment, Helm will render/apply the older release state.
Think:
kubectl rollout undo
→ Deployment-level rollback
helm rollback
→ Helm release-level rollbackRemove a Helm release:
helm uninstall node-apiThis removes resources managed by that release according to Helm's behavior.
Check:
helm listHelm uses the Go template language.
The basic syntax is:
{{ ... }}Example:
replicas: {{ .Values.replicaCount }}.Values#This is one of the most important Helm concepts.
Suppose:
## values.yaml
replicaCount: 3Template:
replicas: {{ .Values.replicaCount }}Result:
replicas: 3Nested:
image:
repository: myregistry/node-api
tag: "2.0.0"Template:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"Result:
myregistry/node-api:2.0.0if Conditions#You can conditionally create resources.
Values:
ingress:
enabled: trueTemplate:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
...
{{- end }}If:
enabled: truethe resource is generated.
If:
enabled: falseit isn't.
if Is Useful#Development:
ingress:
enabled: falseProduction:
ingress:
enabled: trueSame chart.
Different configuration.
Same Chart
│
┌──────────┴──────────┐
↓ ↓
Dev Prod
Ingress OFF Ingress ONwith#Helm has with for changing the current context.
For example:
{{- with .Values.image }}
repository: {{ .repository }}
tag: {{ .tag }}
{{- end }}Instead of repeatedly writing:
.Values.image.repository
.Values.image.tagInside with, . refers to .Values.image.
range#range is useful for lists.
Values:
env:
- name: NODE_ENV
value: production
- name: LOG_LEVEL
value: infoTemplate:
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}Result:
env:
- name: NODE_ENV
value: "production"
- name: LOG_LEVEL
value: "info"Helm supports reusable template helpers.
File:
templates/_helpers.tplExample:
{{- define "node-api.name" -}}
node-api
{{- end -}}Then:
name: {{ include "node-api.name" . }}This avoids repeating naming logic.
include#Example:
{{ include "node-api.fullname" . }}This allows reusable helper templates.
Common Helm charts use helpers for:
name
fullname
labels
selectorLabels
serviceAccountNameHelm provides many template functions.
Common ones include:
quote
default
toYaml
nindent
include
required
upper
lower
trimExample:
image: {{ .Values.image.repository | quote }}default#Suppose:
service:
type: ""You can use:
{{ .Values.service.type | default "ClusterIP" }}If no value is supplied:
ClusterIPis used.
required#Suppose production requires a database URL.
You can write:
{{ required "database.url is required" .Values.database.url }}If missing:
Helm rendering failsThis is much better than deploying a broken configuration.
toYaml#Suppose:
resources:
requests:
cpu: "250m"
memory: "256Mi"You can render:
{{- toYaml .Values.resources | nindent 12 }}This converts the object into YAML with proper indentation.
This pattern appears frequently in real Helm charts.
Let's build a simplified chart:
node-api/
├── Chart.yaml
├── values.yaml
└── templates/
├── deployment.yaml
├── service.yaml
└── ingress.yamlChart.yaml#apiVersion: v2
name: node-api
description: Node.js API Helm chart
type: application
version: 1.0.0
appVersion: "2.1.0"values.yaml#replicaCount: 3
image:
repository: myregistry/node-api
tag: "2.1.0"
pullPolicy: IfNotPresent
containerPort: 3000
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
host: api.example.com
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"deployment.yaml#apiVersion: apps/v1
kind: Deployment
metadata:
name: node-api
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: node-api
template:
metadata:
labels:
app: node-api
spec:
containers:
- name: node-api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.containerPort }}
resources:
{{- toYaml .Values.resources | nindent 12 }}service.yaml#apiVersion: v1
kind: Service
metadata:
name: node-api
spec:
type: {{ .Values.service.type }}
selector:
app: node-api
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: {{ .Values.containerPort }}Notice:
Service port
↓
Valuesand:
Target port
↓
Node.js container portingress.yaml#{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: node-api
spec:
ingressClassName: {{ .Values.ingress.className }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: node-api
port:
number: {{ .Values.service.port }}
{{- end }}Now:
ingress:
enabled: falsemeans:
Don't render the Ingress.
Run:
helm template node-api ./node-apiYou'll see generated Kubernetes YAML.
This is an excellent learning exercise.
Try changing:
replicaCount: 5Then:
helm template node-api ./node-apiYou'll see:
replicas: 5helm install node-api ./node-apiThen:
kubectl get podsYou should see something like:
node-api-xxxxx
node-api-yyyyy
node-api-zzzzzChange:
image:
tag: "2.2.0"Then:
helm upgrade node-api ./node-apiKubernetes performs the Deployment rollout.
So:
Helm
↓
Rendered YAML
↓
Deployment changes
↓
Rolling Update
↓
New PodsThis connects Chapter 24 directly with Helm.
A realistic repository might look like:
helm/
└── node-api/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-staging.yaml
├── values-prod.yaml
│
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── hpa.yaml
├── configmap.yaml
├── serviceaccount.yaml
└── _helpers.tplThen:
helm upgrade --install node-api ./helm/node-api \
-f ./helm/node-api/values-dev.yamlhelm upgrade --install node-api ./helm/node-api \
-f ./helm/node-api/values-prod.yamlupgrade --install means:
Upgrade if the release exists; otherwise install it.
This is very common in deployment automation.
A Helm chart can depend on other charts.
For example:
My Application
│
├── PostgreSQL
├── Redis
└── Other dependencyYou can declare dependencies in Chart.yaml.
Conceptually:
dependencies:
- name: redis
version: "..."
repository: "..."Then Helm can manage chart dependencies.
However, production teams often choose managed cloud databases/caches instead of running every stateful dependency inside the same application chart.
Charts can be packaged and distributed.
Example:
helm package node-apiThis can produce:
node-api-1.0.0.tgzA chart repository can then distribute charts to teams.
Conceptually:
Chart
↓
Package
↓
Chart Repository
↓
helm installDon't confuse these.
Contains your application:
Node.js
+
Application code
+
DependenciesExample:
node-api:2.1.0Contains Kubernetes deployment configuration:
Deployment
Service
Ingress
HPA
ConfigMap
RBAC
etc.Example:
node-api-chart:1.0.0So:
Docker Image → Application
Helm Chart → How Kubernetes deploys the applicationdeployment.yaml
service.yaml
ingress.yaml
hpa.yaml
...Advantages:
Disadvantages:
Advantages:
Disadvantages:
You'll learn Kustomize in Chapter 26.
For now:
Templates
+
ValuesBase YAML
+
Overlays / patchesThink:
Helm
→ Template-based packaging
Kustomize
→ Overlay-based customizationNeither is universally superior.
Don't put plaintext production secrets into:
values-prod.yamlFor example, avoid:
databasePassword: SuperSecret123in Git.
Helm templates can reference Kubernetes Secrets, but you still need a secure strategy for supplying secret values.
Common production approaches include:
External Secrets
Cloud secret managers
Vault
Sealed Secrets
CI/CD secret injectionWe'll revisit security patterns when connecting Helm with CI/CD.
Suppose deployment failed.
Start:
helm listThen:
helm status node-apiRender locally:
helm template node-api ./node-apiDebug rendering:
helm template node-api ./node-api --debugThen inspect Kubernetes:
kubectl get pods
kubectl describe pod <pod>
kubectl logs <pod>Remember:
Helm problems and Kubernetes problems can be different.
For example:
Helm renders invalid YAML
↓
Kubernetes rejects itor:
Helm succeeds
↓
Deployment created
↓
Pod crashesIn the second case, Helm worked; the application/Kubernetes workload has a problem.
A useful mental model:
helm install
↓
Release revision 1
↓
helm upgrade
↓
Revision 2
↓
helm upgrade
↓
Revision 3
↓
Problem
↓
helm rollback
↓
Revision restoredSo Helm tracks release history.
helm versionhelm create node-apihelm listhelm list -Ahelm install node-api ./node-apihelm upgrade node-api ./node-apihelm upgrade --install node-api ./node-apihelm template node-api ./node-apihelm install node-api ./node-api --dry-runhelm status node-apihelm history node-apihelm rollback node-api 2helm uninstall node-apihelm package ./node-apiHelm is a package manager and release-management tool for Kubernetes that uses charts and templates to package and deploy Kubernetes resources.
A reusable package containing Kubernetes templates, configuration, and metadata.
values.yaml?#The default configuration values used by Helm templates.
A deployed instance of a Helm chart.
.Values mean?#It accesses values supplied to the Helm template system.
Example:
{{ .Values.replicaCount }}helm template node-api ./node-apihelm upgrade node-api ./node-apihelm rollback node-api <revision>Helm → package/template/release management
Kubernetes → orchestration/runtimeDocker image → application package
Helm chart → Kubernetes deployment packageHelm → templates + values
Kustomize → bases + overlays/patchesTo make deployments reproducible and rollback/version identification predictable.
The most important structure:
node-api/
│
├── Chart.yaml
│ ↓
│ Metadata
│
├── values.yaml
│ ↓
│ Configuration
│
└── templates/
↓
Kubernetes templatesThe flow:
values.yaml
+
templates
↓
Helm
↓
Rendered YAML
↓
Kubernetes
↓
Deployment
↓
PodsCommands:
helm create node-api
helm install node-api ./node-api
helm upgrade node-api ./node-api
helm upgrade --install node-api ./node-api
helm template node-api ./node-api
helm status node-api
helm history node-api
helm rollback node-api 2
helm uninstall node-apiWithout Helm:
Dev YAML
Staging YAML
Production YAML
↓
Lots of duplicationWith Helm:
Helm Chart
│
┌────────┼────────┐
↓ ↓ ↓
Dev Staging Prod
│ │ │
values values values
│ │ │
└────────┼────────┘
↓
Kubernetes YAML
↓
KubernetesAnd the complete production chain is now:
Code
↓
Docker Image
↓
Helm Chart
↓
Helm Release
↓
Deployment
↓
ReplicaSet
↓
Pods
↓
Service
↓
Ingress
↓
UsersYou've now learned enough Kubernetes fundamentals that Helm is no longer just a separate tool—it fits directly into the deployment architecture we've been building throughout this course.
We'll learn the other major Kubernetes configuration tool:
kustomization.yamlkubectl apply -kMark it as complete to track your mastery.