cloud-devops

helm upgrade --install: Idempotent Deploys (2026)

July 1, 2026

helm upgrade --install: Idempotent Deploys (2026)

helm upgrade --install is one command that installs a release if it doesn't exist yet and upgrades it if it does. The --install (-i) flag makes the deploy safe to re-run, which is why it's the standard idempotent command for CI/CD pipelines.12

TL;DR

Instead of branching between helm install (first deploy) and helm upgrade (every deploy after), you run a single helm upgrade --install NAME CHART everywhere. Helm checks whether a release with that name exists: if not, it installs; if so, it upgrades.1 It is idempotent in the normal case, with one important exception — a failed first install leaves a stuck release and the next run errors with has no deployed releases.34 This guide is written for Helm 4 (GA November 12, 2025; latest patch 4.2.2 at the time of writing), which renamed several flags that older tutorials still get wrong — --atomic is now --rollback-on-failure, --wait and --dry-run take values, and --force became --force-replace.516

What you'll learn

  • What helm upgrade --install actually does and why it's idempotent
  • When to reach for helm install vs helm upgrade
  • The exact reason helm upgrade --install sometimes fails with has no deployed releases, and how to fix it
  • How --create-namespace works with --install
  • What changed for these flags in Helm 4
  • --reuse-values vs --reset-values vs --reset-then-reuse-values and the "my values disappeared" trap
  • How to preview and auto-roll-back a deploy with --dry-run, --wait, and --rollback-on-failure
  • A copy-paste CI/CD pattern for GitHub Actions

What does helm upgrade --install do?

It runs an install when the release is absent and an upgrade when it's present — in a single command. The behavior comes entirely from the -i, --install flag, which the official reference defines as "if a release by this name doesn't already exist, run an install."1

First, confirm you're on Helm 4:

helm version
# version.BuildInfo{Version:"v4.2.2", ...}

Then deploy the same way whether it's the first time or the fiftieth:

helm upgrade --install myapp ./charts/myapp \
  --namespace prod --create-namespace

On the first run Helm creates release myapp at revision 1. On later runs it computes a diff and bumps the revision.7 Every install, upgrade, or rollback increments the revision by 1, and Helm keeps the last 10 revisions per release by default (--history-max, 0 for unlimited).1 You can inspect the history at any time:

helm history myapp -n prod
helm status myapp -n prod

helm install vs helm upgrade: when do you use each?

Use helm install for a brand-new release and helm upgrade to change an existing one — but in automation you rarely want to choose. helm install fails if the release name is already taken, and a plain helm upgrade fails if the release doesn't exist yet.7 helm upgrade --install collapses both into one command that never cares which situation it's in, which is exactly what you want in a script that might run against a fresh cluster or an existing one.2

There is still a place for the standalone commands. Reach for helm install when you explicitly want the run to fail loudly if the release already exists (for example, to guard against clobbering something), and for helm upgrade without --install when you want a deploy to fail rather than silently create a release that was supposed to already be there.

Is helm upgrade --install idempotent?

Mostly yes — running it repeatedly with the same inputs converges on the same state — but there's one sharp edge. The -i flag only triggers an install when no release exists at all. If your very first helm upgrade --install fails partway through, Helm still records a release, just with a status of failed or pending-install rather than deployed.3

On the next run, --install sees that a release named myapp already exists, so it takes the upgrade path. Upgrade needs a revision in the deployed state to use as its baseline, finds none, and stops:

Error: UPGRADE FAILED: "myapp" has no deployed releases

So the command is idempotent once you have a healthy first deployment, but a broken first install can wedge it. The next section covers the fix.

Why does helm upgrade --install fail with "has no deployed releases"?

Because helm upgrade requires a previous revision whose status is deployed to diff against, and after a failed first install there isn't one.4 When every stored revision is failed, pending-install, or pending-upgrade, Helm has no baseline and refuses to continue. This guard was added back in Helm 2.7.1 and is still present in Helm 3 and Helm 4.4

You have three ways out, from cleanest to hackiest:

# 1. Cleanest: remove the stuck release, then let --install do a fresh install
helm uninstall myapp -n prod
helm upgrade --install myapp ./charts/myapp -n prod

# 2. Prevent it next time: roll back automatically on failure (see below)
helm upgrade --install myapp ./charts/myapp -n prod --rollback-on-failure

# 3. Last resort: patch the release Secret's `status` label back to `deployed`
kubectl -n prod get secret -l owner=helm,name=myapp
# then edit the newest sh.helm.release.v1.myapp.v<N> secret's status label

Option 2 is the real prevention: if the first install fails and --rollback-on-failure is set, Helm uninstalls the failed release, leaving nothing stuck for the next run to trip over.1 This error is especially common with Flux HelmRelease objects, where a single bad first reconcile leaves the release wedged until you uninstall it.4 If you run GitOps, our walkthrough of GitOps workflow patterns from commit to cluster covers where this fits in a reconcile loop.

How do I create the namespace with helm upgrade --install?

Add --create-namespace, which the docs define as "if --install is set, create the release namespace if not present."1 It only does anything alongside --install, so it pairs naturally with this command:

helm upgrade --install myapp ./charts/myapp \
  --namespace prod --create-namespace

Without it, deploying into a namespace that doesn't exist yet fails. This is a common first-run stumble, because plain helm install in Helm 2 used to create namespaces implicitly, whereas Helm 3 and 4 return an error to match the behavior of other Kubernetes tooling.8

What changed for these flags in Helm 4?

Helm 4 renamed or restructured several flags you almost always pair with helm upgrade --install, and older guides still show the Helm 3 spellings.51 Here's the mapping that matters:

Helm 3Helm 4Notes
--atomic--rollback-on-failureOld --atomic still works on upgrade but is deprecated in favor of --rollback-on-failure. A regression that broke the deprecated --atomic on helm install (an "unknown flag" error) was fixed in 4.1.3.6
--wait (boolean)--wait WaitStrategyValues: watcher, hookOnly, legacy. Bare --wait = watcher; omitting the flag defaults to hookOnly.1
--dry-run (boolean)--dry-run stringValues: none (default), client, server.1
--force--force-replacePlus a new --force-conflicts for server-side apply conflicts.1

Two of these are easy to get subtly wrong. First, --wait no longer means "wait for my Deployment to be ready" by default — if you omit it, Helm 4 uses the hookOnly strategy and does not wait for your workload pods.1 Pass --wait explicitly to get the kstatus-based watcher behavior that waits for resources to become ready.5 Second, Helm 4 added server-side apply: helm install defaults --server-side to true, while helm upgrade defaults it to auto (inheriting the previous release's method).91

--reuse-values vs --reset-values vs --reset-then-reuse-values

These three flags control which values an upgrade starts from, and the default behavior surprises people. Here's what each one does, per the reference:1

  • --reset-values: reset to the values built into the chart, ignoring what the last release used.
  • --reuse-values: reuse the last release's values and merge in any --set / -f overrides from this command.
  • --reset-then-reuse-values: reset to the chart's values, re-apply the last release's values, then merge this command's overrides. Added in Helm 3.14.0.

The trap is the default when you pass none of them. If your upgrade command includes any value flag (--set, --set-string, --set-file, --values/-f), Helm implicitly uses the reset strategy — so any ad-hoc values you set on a previous deploy but didn't repeat this time are wiped.10 If you pass no value flags at all, Helm implicitly reuses the previous values.10

# Deploy 1: set a replica count
helm upgrade --install myapp ./charts/myapp -n prod --set replicaCount=4

# Deploy 2: change only the image tag, WITHOUT repeating replicaCount
helm upgrade --install myapp ./charts/myapp -n prod --set image.tag=1.5.0
# replicaCount silently reverts to the chart default here (reset strategy)

--reuse-values avoids that revert, but has its own catch: it also ignores any new keys added to the chart's values.yaml in the version you're upgrading to. --reset-then-reuse-values is usually what people actually want — it keeps your previous overrides while still picking up new chart defaults.10 The most robust fix, though, is to stop relying on --set and keep every override in a version-controlled values file that you pass with -f on every deploy.

How do I preview an upgrade before it happens?

Use --dry-run=client to render the release without touching the cluster, optionally with --debug to see the full manifest. In Helm 4 --dry-run takes a mode: client "simulates the operation client-side only and avoids cluster connections," while server runs the simulation against the API server.1

# Client-side render, no cluster connection needed
helm upgrade --install myapp ./charts/myapp -n prod \
  --dry-run=client --debug

# Hide rendered Secrets in the output
helm upgrade --install myapp ./charts/myapp -n prod \
  --dry-run=client --hide-secret

For a pure template render with no release logic at all, helm template ./charts/myapp also works entirely offline.7

How do I make a failed deploy roll back automatically?

Combine --wait with --rollback-on-failure and a --timeout. With --rollback-on-failure set, "Helm will rollback the upgrade to previous success release upon failure," and it automatically defaults --wait to the watcher strategy so Helm actually watches your resources become ready before declaring success.1

helm upgrade --install myapp ./charts/myapp \
  --namespace prod --create-namespace \
  --wait --rollback-on-failure --timeout 5m

If the pods never reach ready within --timeout (default 5m0s), Helm marks the release failed and rolls back to the last good revision.1 For deployments where you also spin up Jobs, add --wait-for-jobs so Helm waits for those Jobs to complete too.1 This pairs well with the readiness practices in our guide to zero-downtime Kubernetes deployments.

How do I use helm upgrade --install in CI/CD?

Run the exact same command in your pipeline that you'd run by hand — its idempotency is what makes it CI-safe.2 Because the command behaves identically whether the release exists or not, you never need conditional logic in your workflow.

name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Install Helm 4
        run: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash
      - name: Deploy (idempotent)
        run: |
          helm upgrade --install myapp ./charts/myapp \
            --namespace prod --create-namespace \
            --wait --rollback-on-failure --timeout 5m

One CI gotcha worth knowing: the popular azure/setup-helm action installs Helm on the runner, but its README states "v2+ of this action only support Helm3," and it falls back to a hardcoded default of v3.18.4 if version detection fails.11 If you specifically need Helm 4 on the runner, install it with the official get-helm-4 script as shown above rather than relying on the action's default.12 For structuring the surrounding pipeline, see our comparison of reusable workflows vs composite actions in GitHub Actions.

Bottom line

helm upgrade --install is the right default for almost every deploy: one idempotent command that installs or upgrades without you branching on the release's current state.2 On Helm 4, pair it with --create-namespace for first runs, --wait --rollback-on-failure --timeout for safe automated deploys, and a version-controlled -f values.yaml to sidestep the value-merge surprises. The two things that bite people — the has no deployed releases wedge after a failed first install, and the renamed Helm 4 flags — are both avoidable once you know they exist.

Next, harden the deploy itself with zero-downtime Kubernetes deployment strategies, wire it into a pipeline with reusable GitHub Actions workflows, or manage it declaratively using GitOps commit-to-cluster patterns.

Footnotes

  1. Helm, "helm upgrade" command reference (v4.2.2). https://helm.sh/docs/helm/helm_upgrade/ 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

  2. Coder Society, "13 Best Practices for using Helm." https://codersociety.com/blog/articles/helm-best-practices 2 3 4

  3. helm/helm issue #3353, "helm upgrade --install doesn't perform an install/upgrade if the first ever install fails." https://github.com/helm/helm/issues/3353 2 3

  4. phoenixNAP, "Fix 'helm has no deployed releases' Error." https://phoenixnap.com/kb/helm-has-no-deployed-releases 2 3 4 5 6

  5. Helm, "Helm 4 Released" (November 17, 2025). https://helm.sh/blog/helm-4-released/ 2 3

  6. helm/helm issue #31900, "helm install with --atomic no longer works in Helm 4." https://github.com/helm/helm/issues/31900 2 3

  7. Helm, "Using Helm." https://helm.sh/docs/intro/using_helm/ 2 3

  8. Helm, "Changes Since Helm 2" (namespace creation). https://helm.sh/docs/v3/faq/changes_since_helm2/

  9. Helm, "helm install" command reference (v4). https://helm.sh/docs/helm/helm_install/

  10. H. Cao, "Understand helm upgrade flags — reset-values & reuse-values." https://medium.com/@kcatstack/understand-helm-upgrade-flags-reset-values-reuse-values-6e58ac8f127e 2 3 4

  11. Azure/setup-helm, GitHub Action README. https://github.com/Azure/setup-helm

  12. Helm, "Installing Helm." https://helm.sh/docs/intro/install/

Frequently Asked Questions

Yes. The -i, --install flag runs an install when no release by that name exists, and an upgrade when one does. 1