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.

By the JobSupport4U engineering team · Updated · 7 min read

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:

Shell
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 running

The 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:

Shell
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:

Shell
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 container

Typical 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:

Shell
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

Shell
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 --debug

The 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 --atomic with 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-max so release Secrets don’t pile up.
Keep reading

More guides