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

SymptomCauseWhat to do
Release stuck pending-upgradeA previous operation was interruptedRoll back to the last good revision, then retry
CRDs not updated after upgradeHelm does not upgrade CRDs in the crds directoryApply the new CRDs manually, then upgrade
Values silently ignoredKey nested at the wrong level or renamed upstreamCompare against helm show values for that exact version
Immutable field errorChart changed a selector or a claim templateDelete and recreate the object, planning for the data
Resources left behind on uninstallPVCs and CRDs are intentionally retainedRemove 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.

Further Reading