Helm 'has no deployed releases': recovering a broken release history
The deploy pipeline has been red for twenty minutes. Every run ends the same way:
Error: UPGRADE FAILED: "checkout-api" has no deployed releases
So you do the obvious thing and roll back. Revision 41 fails. Revision 40 fails. Revision 39, same message. At some point it dawns on you that Helm is not refusing a particular revision, it is refusing all of them, and the rollback you were counting on as your safety net does not exist.
This is not a chart problem and it is not you holding Helm wrong. Both helm upgrade
and helm rollback need a revision marked deployed to use as a baseline for
the diff. If every revision in the history is failed or pending-*, Helm has
no baseline and refuses to do anything, including the one operation that would get you out. There is
still no first-class command to repair this, which is why helm/helm#4349
has been open for years.
run helm history <release> -n <ns>. If no
row says deployed, rollback cannot help you. Pick the revision that matches what is
actually running and mark its release Secret as deployed:
kubectl -n prod patch secret sh.helm.release.v1.checkout-api.v39 \
--type=merge -p '{"metadata":{"labels":{"status":"deployed"}}}'
Then run helm upgrade --atomic again. If nothing from the chart is actually running,
a clean helm uninstall followed by helm install is faster and safer than
patching. Prevent the whole class of problem by putting --atomic on every upgrade.
The exact errors you will see
Error: UPGRADE FAILED: "checkout-api" has no deployed releases
Error: "checkout-api" has no deployed releases
Error: cannot re-use a name that is still in use
The first comes from helm upgrade, the second from helm rollback, and
the third from someone trying helm install as a way out while the broken release is
still registered. All three describe the same underlying state.
Why this happens: Helm's history is a set of labelled Secrets
Helm 3 has no server component. Every revision of a release is stored as a Kubernetes Secret in
the release namespace, of type helm.sh/release.v1 and named
sh.helm.release.v1.<release>.v<revision>. The chart, the values and the
rendered manifest are gzipped and base64-encoded into the Secret's data. The bit that matters here
lives outside that blob, in the Secret's labels: name, owner=helm,
status and version.
When you run an upgrade, Helm asks its storage layer for the currently deployed release. That
lookup is a plain label query for name=<release>, owner=helm and
status=deployed. If the query returns nothing, Helm returns the "has no deployed
releases" error and stops before it renders anything. Rollback takes the same path, which is why
rolling back to an older number does not help: the number is not the problem, the missing label
is.
Two things get you here. The common one is a run of failed deploys: the first upgrade fails and is
marked failed, and every subsequent attempt fails the same way and adds another
failed revision, pushing the last good one further down. The second is retention. The
--history-max flag on helm upgrade defaults to 10, and if someone has set it
to 2 or 3 to keep etcd tidy, a short burst of failures is enough to prune the only
deployed revision out of existence.
The fix
Step 1: Look at the history before you touch anything
helm history checkout-api -n prod
Expected output when you are in this hole:
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
39 Mon Jul 13 21:04:11 2026 failed checkout-api-1.8.2 1.8.2 Upgrade "checkout-api" failed
40 Mon Jul 13 21:19:57 2026 failed checkout-api-1.8.3 1.8.3 Upgrade "checkout-api" failed
41 Mon Jul 13 22:02:40 2026 pending-upgrade checkout-api-1.8.3 1.8.3 Preparing upgrade
42 Mon Jul 13 22:31:08 2026 failed checkout-api-1.8.4 1.8.4 Upgrade "checkout-api" failed
Read the STATUS column, not the revision numbers. If a single row says
deployed, you do not have this problem and something else is wrong. If one says
pending-upgrade or pending-install and you are also seeing "another
operation (install/upgrade/rollback) is in progress", deal with
the stuck pending release first, because
the two failures often arrive together.
Step 2: Find out what is actually running
This decides which repair you use, and it is the step people skip.
kubectl -n prod get all -l app.kubernetes.io/instance=checkout-api
If that returns nothing, or only broken pods that never became ready, the release is dead and the
honest fix is to remove it and install again. If it returns healthy pods serving live traffic, do not
run helm uninstall. Patch instead.
Step 3: Mark one revision as deployed
List the release Secrets with their labels so you can see the state Helm sees:
kubectl -n prod get secret -l owner=helm,name=checkout-api \
--sort-by=.metadata.creationTimestamp --show-labels
Choose the revision whose rendered manifest matches what is running now. In practice that is the last revision before the breakage started, or, if a failed upgrade did apply its changes before timing out on readiness, the most recent one. When in doubt, dump it and read it:
helm get manifest checkout-api -n prod --revision 39 | head -40
Then patch its label:
kubectl -n prod patch secret sh.helm.release.v1.checkout-api.v39 \
--type=merge -p '{"metadata":{"labels":{"status":"deployed"}}}'
Expected output: secret/sh.helm.release.v1.checkout-api.v39 patched.
Two things to be precise about here. First, exactly one revision should carry
status=deployed; if you patch several, Helm's behaviour becomes unpredictable. Second,
this patch changes the label that the lookup queries, not the status recorded inside the gzipped
release object in the Secret's data. That is why helm history may still print
failed for the revision you just patched while helm upgrade works fine
again. It is cosmetic, and it corrects itself on the next successful upgrade, which supersedes the
old revisions and writes a fresh one.
Step 4: Upgrade again, with a seatbelt this time
helm upgrade checkout-api ./charts/checkout-api -n prod \
--atomic --timeout 10m --history-max 20
--atomic waits for resources to become ready and, on failure, rolls the release back
to the previous revision automatically. That rollback is what keeps a deployed revision
in the history, which is precisely the property you just lost. Note that --atomic implies
--wait, so a chart with a workload that never becomes ready will now fail slowly instead
of failing fast. That is the trade, and it is worth it.
Step 5: The clean-slate path, for when nothing is running
helm uninstall checkout-api -n prod
helm install checkout-api ./charts/checkout-api -n prod --atomic --timeout 10m
Be very clear about what helm uninstall deletes. Everything in the release manifest,
including Services (so any LoadBalancer IP is likely to change) and, depending on your chart,
PersistentVolumeClaims. Read the manifest first. On a stateful chart in production this is an
outage, not a repair.
There is a middle path if you must detach Helm from live resources without deleting them: delete
only the release Secrets, then reinstall while letting Helm adopt the existing objects. Helm will
refuse to take ownership unless each resource carries the label
app.kubernetes.io/managed-by=Helm and the annotations
meta.helm.sh/release-name and meta.helm.sh/release-namespace. Adding those
by hand across a dozen resources is fiddly and easy to get wrong, so keep this for the case where an
outage is genuinely unacceptable.
Verification
helm history checkout-api -n prod
helm status checkout-api -n prod
You want exactly one row in deployed state, at the highest revision number, and
helm status reporting STATUS: deployed. Then prove the escape hatch is back
before you need it:
helm rollback checkout-api --dry-run -n prod
If that plans a rollback instead of erroring, your history is healthy again. Finally, confirm only one Secret claims the deployed label:
kubectl -n prod get secret -l owner=helm,name=checkout-api,status=deployed
What people get wrong
Rolling back to progressively older revisions. The single most common reaction, and it cannot work. Rollback resolves the deployed release using the same query that is already returning nothing. Ten attempts produce ten identical errors and a lot of lost time.
Reaching for --force. On helm upgrade,
--force replaces resources rather than patching them, which will happily delete and
recreate a Service or fail outright on immutable fields in a StatefulSet. It does not create a
deployed baseline, so it does not address this error at all.
Uninstalling on reflex. In a dev namespace, uninstall and reinstall is the right call and takes thirty seconds. In production, running it against a chart with a PVC or a LoadBalancer, it turns a recoverable metadata problem into a genuine incident. Always run step 2 first.
Retrying the failed deploy in CI. Every automatic retry appends another
failed revision and pushes the last good one closer to the retention cliff. If your
pipeline retries Helm upgrades, cap it at one attempt and make the second failure page a human.
Setting --history-max very low to save space. Release Secrets are
small. A limit of 2 or 3 is how a bad afternoon becomes an unrecoverable history. Ten is the default
for a reason; twenty is a reasonable choice for a busy service.
When it is still broken
- Check your storage backend. If
HELM_DRIVERis set toconfigmaporsql, your history is not in Secrets at all and the patch above targets the wrong object. Runkubectl -n prod get configmap -l owner=helmand adjust. - Check the namespace. Helm resolves releases per namespace. A release that "does
not exist" is very often a missing
-nin a pipeline where the default context changed. Confirm withhelm list -A. - Check for a stuck pending status. A revision left in
pending-upgradeby a Helm process that was killed mid-flight blocks new operations with a different message, and needs its own fix. - Read why the upgrade failed in the first place. You have restored the ability to
deploy, not fixed the deploy.
kubectl -n prod describe podandkubectl -n prod get events --sort-by=.lastTimestampwill usually show the failed readiness probe, missing image or unbound claim that started all of this. If it is storage, my notes on unbound PersistentVolumeClaims cover the usual suspects.
The prevention is genuinely simple and almost nobody does it: put --atomic and a
realistic --timeout on every single upgrade, in every pipeline, from the first day of a
project. A failed deploy then rolls itself back, the history always contains a deployed revision, and
you never read this error again.
Frequently asked questions
- What does 'has no deployed releases' mean in Helm?
- It means no revision in the release history carries the status 'deployed', so Helm has no baseline to diff the new manifest against. Helm 3 stores each revision as a Secret labelled with name, owner and status, and both upgrade and rollback look up the deployed revision by that label query. If every revision is failed or pending, the query returns nothing and both commands refuse to run.
- Why does helm rollback also fail with 'has no deployed releases'?
- Because rollback resolves the current deployed release using the same label query that upgrade uses. Trying progressively older revision numbers changes nothing, since the revision number is not what Helm is missing. You have to restore a deployed status first, either by patching the release Secret or by uninstalling and installing fresh.
- Is it safe to patch a Helm release Secret to status deployed?
- It is safe as long as you patch exactly one revision, and you choose the revision whose rendered manifest matches what is actually running in the cluster. The patch changes only the label that Helm queries, so helm history may still display the old status until the next successful upgrade supersedes it. Verify with 'helm get manifest <release> --revision N' before patching.
- How do I stop Helm release history from breaking again?
- Run every upgrade with --atomic and a realistic --timeout, so a failed upgrade rolls itself back and always leaves a deployed revision in the history. Do not set --history-max to a very low number, since a short run of failures can then prune the last good revision away. In CI, avoid blindly retrying failed Helm upgrades, because each retry appends another failed revision.