The package manager label leads people to expect dependency resolution and installed software. What you get instead is a templating engine with a memory, and every confusing behaviour, from ignored values to CRDs that quietly do not upgrade, follows from that. Here is the model that makes it predictable, plus an honest account of when plain manifests are the better tool.
Helm is described as a package manager, which sets the wrong expectation. It installs nothing on a machine and resolves no dependencies at runtime. What it actually does is render templates into Kubernetes manifests and remember what it rendered, and understanding it that way makes its behaviour predictable.
Chart, Values, Release
Three terms carry the whole model.
A chart is a directory of templated manifests plus metadata and defaults. It is inert. Nothing about a chart is specific to your cluster until it is rendered.
Values are the inputs. The chart ships defaults in values.yaml, you override the ones you care about, and the merged result is what the templates see.
A release is one installation of a chart with a particular set of values, under a name you chose. The same chart installed twice with different names is two releases, which is how you run staging and production side by side.
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm show values bitnami/postgresql > my-values.yaml
helm install db bitnami/postgresql -f my-values.yaml -n data --create-namespace
The third line is the habit worth forming. Dump the full default values to a file, edit that file, and keep it in version control. Passing a stack of --set flags works and leaves no record of what you configured.
Render Before You Install
The single most useful command in Helm produces no changes at all.
helm template db bitnami/postgresql -f my-values.yaml | less
helm diff upgrade db bitnami/postgresql -f my-values.yaml # needs helm-diff plugin
helm template prints exactly the manifests that would be applied. Read them. Charts from third parties routinely create service accounts, cluster-scoped roles, and Services you did not expect, and the values file gives no hint of it.
The helm-diff plugin extends the same idea to upgrades, showing what will change against what is currently deployed. On any cluster carrying real traffic, treat an upgrade without a diff as an untested change.
Where the Release State Lives
Helm stores each release's rendered manifests and values in a Secret in the release namespace, one per revision. This has three practical consequences.
Rollback works because the previous revision's manifests are still there, so helm rollback db 3 reapplies a known-good state without you reconstructing it. Deleting those Secrets orphans the release: the workloads keep running and Helm loses track of them entirely. And a very large chart can bump into the size limit on a Secret, which is a real if uncommon failure.
helm history db -n data
helm rollback db 3 -n data
helm get values db -n data # what was actually set
helm get manifest db -n data # what was actually applied
Those last two commands answer the question "what is deployed and why", which is otherwise a matter of guesswork on a cluster somebody else configured.
The Failure Modes
| Symptom | Cause | What to do |
|---|---|---|
| Release stuck pending-upgrade | A previous operation was interrupted | Roll back to the last good revision, then retry |
| CRDs not updated after upgrade | Helm does not upgrade CRDs in the crds directory | Apply the new CRDs manually, then upgrade |
| Values silently ignored | Key nested at the wrong level or renamed upstream | Compare against helm show values for that exact version |
| Immutable field error | Chart changed a selector or a claim template | Delete and recreate the object, planning for the data |
| Resources left behind on uninstall | PVCs and CRDs are intentionally retained | Remove them deliberately, never as a reflex |
The CRD row causes the most confusion, because the upgrade reports success and the new features simply do not work. It is documented behaviour and it is deliberate, since automatically replacing a CRD can delete every object of that type.
Pinning Is Not Optional
A chart version and the application version inside it are different numbers, and both matter. helm install without --version takes whatever the repository currently offers, which means the same command run two weeks apart deploys two different things.
helm install db bitnami/postgresql --version 16.4.2 -f my-values.yaml -n data
Pin the chart version in the command or in a dependency file, and keep the values file next to it in Git. Between them they describe the deployment completely, which is what makes an environment reproducible and an incident explainable.
When Not to Use Helm
Helm earns its complexity when a chart is being installed repeatedly with different inputs, or when it comes from upstream and you want their packaging rather than your own fork of it. Neither applies to every workload.
For your own applications deployed to two or three environments, plain manifests with Kustomize overlays are frequently the better trade. There is no templating language between you and the YAML, the output is readable, and kubectl diff tells you what will change. Many teams end up with both: Helm for third-party components, Kustomize for their own services, which is a coherent position rather than a compromise.
If you have adopted a GitOps controller, keep Helm as a renderer rather than a deployer. The controller reconciles from Git, Helm turns charts into manifests, and nobody runs helm upgrade from a laptop against production.
The Cluster This Runs On
Helm's state living in Secrets means release history shares the fate of etcd, so a cluster that loses its control plane loses its ability to roll back even though the workloads are still serving. That is an argument for a genuinely redundant control plane rather than a single node, and our guide to building a highly available Kubernetes cluster covers the quorum arithmetic.
On MassiveGRID, managed Kubernetes starts from $0.03474 per hour, about $25.37 a month, with resources billed in cloudlets of 128 MiB RAM and 400 MHz CPU, and the control plane is somebody else's responsibility to keep quorate. Where you run the cluster yourself, Proxmox high-availability clustering restarts a failed node's workloads automatically and Ceph storage replicates every block three times across independent NVMe drives, which keeps etcd and Helm's release Secrets intact through a node loss.
Clusters can be ordered across a partner footprint of more than 700 datacenters in 85 metros, 30 countries and six continents, with auto-provisioning in New York, London, Frankfurt and Singapore.