After a few weeks with Kubernetes, many beginners hit the same wall: the same Deployment, Service, ConfigMap, and Ingress YAML copied across three environments with slightly different names, image tags, and replica counts. kubectl apply -f works until it does not — until someone applies the wrong folder, forgets which version is live, or needs to roll back without a clear history.
Helm is one common answer. It is not magic and not mandatory. It is a package manager for Kubernetes: charts describe a set of resources, releases record what was installed, and values let you customize without editing templates by hand every time.
This post explains what Helm adds, the vocabulary that confuses beginners, the commands you actually use, and when staying with plain YAML is the better choice.
Why Helm exists
Kubernetes is declarative. You describe desired state; controllers reconcile. That part is clean. What Kubernetes does not solve by itself is packaging and lifecycle management across teams and environments.
Without Helm, a typical small application might need:
- Deployment
- Service
- Ingress or Route
- ServiceAccount
- ConfigMap
- Secret references
- HorizontalPodAutoscaler
- PodDisruptionBudget
Multiply that by staging, production, and maybe a tenant-specific variant. You end up maintaining many nearly identical files. Helm groups them into a chart — a directory with templates, default values, and metadata — and installs a release — a named instance of that chart in a namespace.
Helm then gives you:
- One install command that renders templates and applies resources
- Upgrade with revision history
- Rollback to a previous revision
- Values overrides per environment without duplicating entire manifests
Helm does not replace understanding Kubernetes objects. It organizes them. If you cannot read the rendered YAML, Helm will not save you on a bad day.
Core vocabulary
Three words matter most:
Chart — The package. A folder (often versioned in Git or fetched from a repository) containing Chart.yaml, values.yaml, and a templates/ directory. Charts can depend on other charts.
Release — A chart installed into a cluster with a specific name and configuration. You might install checkout from the same chart twice in different namespaces with different values. Each is a separate release.
Values — Input data that templates use to render final manifests. Defaults live in values.yaml. Environment-specific overrides live in separate files or --set flags.
Mental model:
Chart + Values -> rendered manifests -> Release (revision N in cluster)
Revision numbers increment on each successful upgrade. That history is what makes rollback practical.
Anatomy of a simple chart
A minimal chart looks like this:
checkout/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
_helpers.tpl
Chart.yaml names the chart and its version:
apiVersion: v2
name: checkout
description: Checkout web service
type: application
version: 0.3.0
appVersion: "2.1.0"
values.yaml holds defaults:
replicaCount: 2
image:
repository: ghcr.io/example/checkout
tag: "2.1.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
Templates reference values with Go templating:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "checkout.fullname" . }}
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: checkout
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.service.targetPort }}
You do not need to love templating on day one. You need to recognize that Helm generates YAML from these files. When debugging, always look at what the cluster actually received.
Install: create a release
Install a chart into a namespace with a release name:
helm install checkout ./checkout -n my-app --create-namespace
Helm renders templates with default values.yaml and creates resources. Check what happened:
helm list -n my-app
kubectl get deploy,svc -n my-app
helm get manifest checkout -n my-app
helm get manifest shows the rendered YAML applied to the cluster. That command is underrated for learning and debugging.
Override values at install time with a file:
helm install checkout ./checkout -n my-app -f values-staging.yaml
Or with --set for small changes:
helm install checkout ./checkout -n my-app --set replicaCount=3,image.tag=2.2.0
Prefer values files for anything you will repeat. --set is convenient for experiments; files belong in Git for teams.
Upgrade: change configuration safely
When the chart or values change, upgrade the release:
helm upgrade checkout ./checkout -n my-app -f values-staging.yaml
helm history checkout -n my-app
Each successful upgrade creates a new revision. History looks roughly like:
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Tue Sep 16 10:00:00 2026 superseded checkout-0.3.0 2.1.0 Install complete
2 Tue Sep 16 14:30:00 2026 deployed checkout-0.3.1 2.2.0 Upgrade complete
Helm applies a three-way strategic merge patch by default for upgrades. That means it compares the old live manifest, the new rendered manifest, and the previous release metadata. For most beginner workloads this behaves as expected. Edge cases appear with fields modified outside Helm — more on that below.
Use --dry-run before risky changes:
helm upgrade checkout ./checkout -n my-app -f values-prod.yaml --dry-run
Some teams also use helm diff (a plugin) to preview changes. Even without plugins, helm get manifest before and after teaches a lot.
Rollback: return to a previous revision
When an upgrade misbehaves, rollback:
helm rollback checkout 1 -n my-app
helm history checkout -n my-app
Revision 1 here means the first revision, not necessarily “one step back”. Check history first. Rollback creates a new revision that re-applies the old templates and values from the chosen revision.
Rollback is not a substitute for fixing forward in Git. It is emergency steering. The durable fix still belongs in version control: corrected values, chart version, or application image.
Using charts from repositories
You do not always author charts yourself. Many teams install upstream charts:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo nginx
helm install my-nginx bitnami/nginx -n demo --create-namespace
Third-party charts vary in quality. Read default values.yaml, check what resources they create, and pin chart versions in production. helm show values bitnami/nginx helps before install.
For learning, installing a known chart and inspecting helm get manifest is a fast way to see how experienced packagers structure templates.
When plain YAML is enough
Helm is not free complexity. Templates can become hard to read. Debugging requires an extra step — render, then inspect. For some workloads, that overhead buys little.
Plain kubectl apply -k with Kustomize overlays, or separate environment folders in Git, may be simpler when:
- You have one small application and one or two environments
- Changes are infrequent and the team is tiny
- You already use GitOps (Flux, Argo CD) that reconciles plain manifests from a repo
- Nobody needs Helm revision history because Git history is the source of truth
Helm tends to pay off when:
- The same application ships to many environments or tenants
- You reuse a chart across teams with different values
- You depend on upstream charts and want consistent upgrade paths
- Operators expect
helm upgradeandhelm rollbackin runbooks
There is no purity contest. Many clusters use both: Helm for packaged platform components, Kustomize or plain YAML for application repos. Choose based on who maintains the manifests and how often they repeat.
Common beginner mistakes
Treating helm install like kubectl apply without tracking the release name. If you lose the release name or namespace, cleanup gets messy. Document release = checkout, namespace = my-app somewhere obvious.
Editing live resources with kubectl edit and expecting Helm to know. Helm records what it applied. Manual cluster edits create drift. The next upgrade may overwrite your change or behave unexpectedly. Prefer values changes and upgrades, or adopt a strict “Helm owns these labels” policy.
Giant values files with no structure. Split staging and production values. Keep secrets out of Git — use External Secrets, Sealed Secrets, or CI-injected files.
Blind --set strings with commas and dots. Typo-prone for nested keys. Files scale better.
No resource limits in chart defaults. A chart that deploys fine in minikube can overwhelm a shared cluster. Treat defaults as production-shaped when others will reuse the chart.
Installing latest chart version without reading changelog. Upstream chart bumps sometimes change labels, Service names, or required values. Pin versions:
helm install postgres bitnami/postgresql --version 15.2.5 -n data
Forgetting --create-namespace on first install. Helm does not always create the namespace unless asked or configured.
Assuming rollback fixes application data. Rolling back a Deployment image helps. Rolling back a database migration chart may not. Know which resources are stateful.
Inspecting and uninstalling
Useful day-to-day commands:
helm status checkout -n my-app
helm get values checkout -n my-app
helm get values checkout -n my-app --all
helm template checkout ./checkout -f values-staging.yaml
helm uninstall checkout -n my-app
helm template renders locally without touching the cluster — excellent for CI validation and code review.
helm get values --all shows computed values including defaults merged with overrides. When a Pod spec looks wrong, compare that output to what you intended.
Uninstall removes release metadata and resources Helm created with standard labels. Resources you added manually or that lost the release label may remain. Always verify:
kubectl get all -n my-app
Helm in a GitOps world
Flux and Argo CD can deploy Helm charts from Git. The mental model stays the same — chart, values, release, revision. If plain manifests already sync reliably, adding Helm only because it is popular may slow you down. Installing the same platform stack in many clusters is where packaging usually earns its keep.
Closing thought
Helm is a filing system and a revision log for Kubernetes manifests, not a substitute for knowing what a Deployment does.
I reach for it when repetition and environment variance would otherwise drown a repo in copy-paste YAML. I stay with Kustomize or plain manifests when the workload is small and Git history already tells the story.
Learn helm install, upgrade, history, rollback, and get manifest first. Render before you trust. Keep values in Git, secrets out of Git, and treat rollback as a brake pedal — useful in the moment, not the long-term steering plan.
Once those habits are in place, charts feel less like mystery templates and more like what they are: parameterized manifests with a paper trail.