Kubernetes · Helm
Helm upgrade failed: how to debug and roll back
Fix failed Helm upgrades: stuck pending releases, timeouts, “has no deployed releases”, existing resources and immutable fields — plus safe rollbacks and prevention.
A failed helm upgrade in a pipeline is stressful: the release may be half-applied, the next deploy refuses to run, and the error message is rarely the real cause. Here is how to find out what state you’re in, fix the most common errors, and roll back safely.
First, find out what state you’re in
Before changing anything, look at the release and its history:
helm list -n my-namespace -a # -a includes failed and pending releases
helm history my-app -n my-namespace # every revision, its status and description
helm status my-app -n my-namespace # current revision and notes
kubectl get pods -n my-namespace # what is actually runningThe STATUS column tells you where you are: deployed is healthy, failed means the last upgrade failed, and pending-upgrade, pending-install or pending-rollback mean an operation never finished — usually because the pipeline was cancelled or timed out mid-way.
The most common errors and their fixes
“another operation (install/upgrade/rollback) is in progress”
The release is stuck in a pending-* state. If no other deploy is really running, roll back to the last good revision from helm history:
helm rollback my-app 41 -n my-namespace # 41 = last revision with status "deployed" or "superseded"Helm 3 stores each revision as a Kubernetes Secret named sh.helm.release.v1.<release>.v<revision>. Deleting the stuck pending revision’s Secret is a last resort when rollback itself fails — back it up first, and never delete the Secret of the revision that is actually deployed.
“timed out waiting for the condition”
With --wait or --atomic, Helm waits for resources to become ready. A timeout means the new pods never became ready — so debug the pods, not Helm:
kubectl get pods -n my-namespace
kubectl describe pod <pod-name> -n my-namespace # events: image pulls, probes, scheduling
kubectl logs <pod-name> -n my-namespace --previous # logs from the crashed containerTypical causes: a failing readiness probe, a wrong image tag, a missing ConfigMap or Secret, or resource requests that no node can satisfy.
“has no deployed releases”
The very first install of this release failed, so there is no good revision to upgrade from. Once you’ve fixed the cause, uninstall the failed release and install it again — after checking that nothing important (such as a persistent volume) depends on it.
“rendered manifests contain a resource that already exists”
A resource in your chart already exists in the cluster but isn’t owned by this release — it was created by hand, by another release, or by a previous tool. Either delete it so Helm can create it, or let Helm adopt it by adding the ownership metadata Helm expects:
kubectl label <kind> <name> -n my-namespace app.kubernetes.io/managed-by=Helm
kubectl annotate <kind> <name> -n my-namespace meta.helm.sh/release-name=my-app
kubectl annotate <kind> <name> -n my-namespace meta.helm.sh/release-namespace=my-namespace“field is immutable”
Some fields can’t change on an existing object — most often a Deployment’s spec.selector labels, or a Job’s template. Revert the change, or delete and recreate that one resource in a planned window. Avoid changing selector labels in charts once they are in production.
Rolling back safely
helm rollback my-app <revision>creates a new revision with the old configuration — history is preserved.- Rollback restores Kubernetes objects, not data: database migrations, persistent volume contents and external side effects are not undone.
- Helm installs CRDs from a chart’s
crds/directory but does not upgrade or roll them back; manage CRD changes separately.
Catch problems before they deploy
helm lint ./chart -f values-prod.yaml
helm template my-app ./chart -f values-prod.yaml > rendered.yaml # inspect the real manifests
helm upgrade my-app ./chart -f values-prod.yaml --dry-run --debugThe helm-diff plugin shows exactly what an upgrade will change against the live release, which makes it a good step to add to pipelines before the real upgrade.
Prevent stuck releases
- Use
--atomicwith a realistic--timeout: a failed upgrade rolls back automatically instead of leaving a half-applied release. - Stop pipelines from deploying the same release concurrently, and don’t cancel jobs mid-upgrade.
- Give every workload a real readiness probe, so “ready” means ready.
- Keep history bounded with
--history-maxso release Secrets don’t pile up.