Helm 4 (v4.0.0) shipped at KubeCon NA on November 12, 2025, the first major release in six years. The latest stable version is v4.1.4 (April 9, 2026). Migrating from Helm 3 is not optional indefinitely: bug fixes end July 8, 2026, and security patches stop November 11, 2026. This guide covers every breaking change, when they actually affect you, and a phased rollout strategy you can validate in staging before touching production.

Why Migrate Now: Helm 3 EOL Timeline

The Helm maintainers have set hard support cutoffs for Helm 3:

  • Bug fixes end: July 8, 2026 (approximately 8 months after Helm 4 release)
  • Security fixes end: November 11, 2026 (exactly one year after Helm 4)
  • No feature backports to Helm 3, except Kubernetes client library updates

After those dates, you are running an unsupported package manager against production clusters. For most teams, the security patch cutoff is the non-negotiable deadline.

The Kubernetes version support matrix also matters. Each Helm release supports the four most recent Kubernetes minor versions:

Helm releaseSupported Kubernetes versions
Helm 4.1.x (current stable)1.32, 1.33, 1.34, 1.35
Helm 4.0.x1.31, 1.32, 1.33, 1.34
Helm 3.20.x (current)1.29, 1.30, 1.31, 1.32

If your cluster is at Kubernetes 1.33 or later, Helm 3.20.x is already outside the official support window. If you are also planning a Kubernetes upgrade alongside this Helm migration, see the Kubernetes v1.36 Production Upgrade Guide for a pre-upgrade checklist and compatibility matrix.

What Changed in Helm 4: Breaking Changes at a Glance

Before walking through each change in detail, here is the full impact surface:

ChangeHelm 3Helm 4
Default apply method (new installs)Client-side three-way mergeServer-Side Apply
Default apply method (upgrades)Client-side three-way mergeLatches to prior release method
--wait readiness trackingPolls Deployment replicas; ignores CRskstatus watcher; includes CRs
RBAC for --waitlist permissionlist + watch permission
--atomic flag--atomic--rollback-on-failure (old flag retained with deprecation warning)
--force flag--force--force-replace (old flag retained with deprecation warning)
Post-renderers--post-renderer <executable>Plugin with plugin.yaml manifest required
Plugin runtimeSubprocess onlySubprocess + Wasm (extism/v1)
OCI supportExperimental (HELM_EXPERIMENTAL_OCI=1)Stable, always on
Registry login formatAccepts full URLsDomain names only
helm repo update failureSilent, continues to next repoErrors on failure
Content cachingNone~/.cache/helm/content

Flags removed entirely (no deprecation period):

  • --create-pods (testing flag)
  • helm version --client
  • helm repo add --no-update
  • helm status --show-desc and --show-resources

The stable OCI registry support enables digest-based chart installation, which provides cryptographic verification of chart content before install. For teams concerned about the broader Kubernetes supply chain threat landscape, see Securing AI/ML Supply Chains on Kubernetes for how attackers move from a poisoned dependency to cluster-wide compromise, and the K8s-native controls that break the kill chain.

Server-Side Apply: The Biggest Change You Need to Understand

Server-Side Apply (SSA) replaces Helm 3’s client-side three-way strategic merge patch. With SSA, the Kubernetes API server owns the merge logic rather than the Helm client. This gives you better array merging, field ownership tracking, and conflict detection across multiple controllers touching the same resource.

The critical thing to understand is the default behavior:

  • New helm install: SSA is on by default. No flag needed.
  • Existing helm upgrade on a Helm 3 release: Helm reads the prior apply method from release metadata and uses client-side apply automatically. Your existing releases are not changed.
  • Explicit migration to SSA: Pass --server-side=true on the next helm upgrade. Helm stores apply_method: ssa in release metadata, and all subsequent upgrades and rollbacks for that release use SSA.

The flowchart below shows exactly how Helm 4 decides which apply method to use:

graph TD
    A[helm install or upgrade] --> B{Operation?}
    B -->|helm install| C{--server-side flag?}
    B -->|helm upgrade / rollback| D{--server-side flag?}
    C -->|not set| E[Use SSA - default for new installs]
    C -->|true| E
    C -->|false| F[Use CSA]
    D -->|true| E
    D -->|false| F
    D -->|not set: auto| G{Release metadata: apply_method?}
    G -->|ssa| E
    G -->|csa or unset - Helm 3 release| F
    E --> H{Field ownership conflicts?}
    H -->|no conflicts| I[Apply succeeds]
    H -->|conflict + --force-conflicts| J[Override: Helm becomes sole field manager]
    H -->|conflict, no flag| K[Error: conflict details printed]
    F --> I

Helm 4 checks release metadata to decide between SSA and CSA. Existing Helm 3 releases always continue with client-side apply unless you explicitly opt in with --server-side=true.

Dealing with Field Ownership Conflicts

When you migrate an existing release to SSA, you may hit ownership conflicts. These happen when another controller (an operator, kubectl edit, or a GitOps tool) has set a field that Helm now wants to own. Two paths forward:

# Inspect the conflict first with server-side dry-run (recommended)
helm upgrade myrelease ./mychart -n production --server-side=true --dry-run=server

# Resolve by making Helm the sole owner of conflicting fields
helm upgrade myrelease ./mychart -n production --server-side=true --force-conflicts

Use --force-conflicts carefully. It strips field ownership from any other manager that held those fields. If a controller was actively managing a field, that management disappears silently.

Dry-Run Behavior Changed

--dry-run=client does not accurately simulate SSA. For pre-migration validation, use --dry-run=server, which runs the actual API server admission chain and surfaces ownership conflicts before they become production problems:

helm upgrade <release-name> <chart> -n <namespace> --dry-run=server

kstatus and the New —wait Behavior

Helm 4 replaces its internal wait logic with kstatus, a Kubernetes status library that provides event-driven resource readiness tracking. The behavioral differences from Helm 3 are significant enough to change your rollout assumptions.

BehaviorHelm 3Helm 4
Custom Resources during --waitIgnored entirelyWaits for Ready: true condition
Old pod terminationDoes not waitWaits until old pods are fully deleted
Tracking methodPollingEvent-driven watch
Rollout completion definitionNew pods readyNew pods ready + old pods deleted + CRs reconciled
graph LR
    subgraph Helm3["Helm 3 --wait"]
        A3[helm upgrade] --> B3[Poll Deployment replicas]
        B3 --> C3{New pods ready?}
        C3 -->|yes| D3["DONE - old pods may still be running"]
        C3 -->|no| B3
    end
    subgraph Helm4["Helm 4 --wait kstatus"]
        A4[helm upgrade] --> B4[Watch all resources via kstatus]
        B4 --> C4{New pods ready?}
        C4 -->|no| B4
        C4 -->|yes| E4{Old pods deleted?}
        E4 -->|no| B4
        E4 -->|yes| F4{"Custom Resources: Ready condition?"}
        F4 -->|no| B4
        F4 -->|yes| G4["DONE - fully reconciled"]
    end

Helm 4’s kstatus watcher tracks true reconciliation. Helm 3 completed as soon as new pods became ready, while old pods were still running and custom resources were ignored.

—wait Flag Values

# kstatus watcher - default when --wait is specified
helm upgrade myrelease ./mychart -n production --wait

# Explicit kstatus (same as above)
helm upgrade myrelease ./mychart -n production --wait=watcher

# Fall back to Helm 3 legacy wait behavior
helm upgrade myrelease ./mychart -n production --wait=legacy

# No waiting
helm upgrade myrelease ./mychart -n production --wait=false

The RBAC Change That Breaks Silently

kstatus requires the watch verb on resources, not just list. This is the most commonly missed migration blocker: none of the top-ranking Helm 4 guides mention it. If your Helm service account does not have watch permissions, --wait operations fail immediately after upgrading the binary, with no changes to your charts.

Update your ClusterRole before swapping the binary:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: helm-deployer
rules:
  - apiGroups: ["", "apps", "batch"]
    resources: ["*"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

If you use granular per-resource rules rather than wildcards, add watch to every resource type your charts deploy. Validate with:

kubectl auth can-i watch deployments --as=system:serviceaccount:<namespace>:<helm-sa-name>

Migration risk for operator-heavy environments: Charts that deploy custom resources may timeout during --wait if those CRs do not set a Ready condition. kstatus specifically watches for Ready: true. Use --wait=legacy as a fallback while you audit your CRD schemas and CR readiness behavior.

The WebAssembly Plugin System

Helm 4 redesigns the plugin architecture around three explicit plugin types, with WebAssembly as a new native runtime alongside the existing subprocess model.

graph LR
    subgraph Helm3["Helm 3"]
        H3[helm command] -->|"--post-renderer executable"| SH3[Shell subprocess]
        SH3 --> IO3["stdin / stdout"]
    end
    subgraph Helm4["Helm 4"]
        H4[helm command] --> PM4["plugin.yaml manifest"]
        PM4 --> RT4{runtime field}
        RT4 -->|subprocess| SH4["Shell subprocess - backward compat"]
        RT4 -->|"extism/v1"| WA4["Wasm sandbox via Extism"]
        PM4 --> PT4[type field]
        PT4 --> CLI4["cli/v1"]
        PT4 --> DL4["download/v1"]
        PT4 --> POR4["postrenderer/v1"]
    end

Helm 3 used a raw executable subprocess for post-rendering. Helm 4 requires a plugin.yaml manifest and supports both subprocess (backward compatible) and Wasm (sandboxed) runtimes.

What You Need to Migrate

The breaking change: --post-renderer <executable> no longer works. You must package your post-renderer as a plugin with a plugin.yaml manifest. Here is what that manifest looks like using the subprocess runtime, which gives identical behavior to the Helm 3 approach:

apiVersion: v1
type: postrenderer/v1
name: my-postrenderer
version: 0.1.0
runtime: subprocess
runtimeConfig:
  platformCommand:
    - command: ${HELM_PLUGIN_DIR}/my-script.sh

On Wasm post-renderers: As of April 2026, the official Helm documentation marks the Wasm post-renderer tutorial as incomplete (the page exists but the content is a TODO placeholder). If you are building a new post-renderer plugin today, use the subprocess runtime. The Wasm path is technically available via extism/v1, but documentation is sparse and you will need to consult HIP-0026 directly. Watch the Tutorial: Build a Postrenderer Plugin page for when the official guide is published.

Backward Compatibility Window

Existing Helm 3 subprocess-style plugins continue to work in Helm 4. They are deprecated but the maintainers have committed to supporting them through the entire Helm 4 lifecycle, with removal planned for Helm 5. Build all new plugins with plugin.yaml from the start, and migrate existing ones when your schedule allows.

Non-local plugin installations now require plugin signing. Use --allow-insecure-plugins to bypass signing during development, but require signing in production pipelines.

CRD Handling: What Changed and What Didn’t

CRD lifecycle behavior is the most-requested feature change in Helm’s issue tracker. It remains intentionally unchanged in Helm 4:

  • CRDs in the crds/ directory are applied on helm install only, not on helm upgrade
  • CRDs that already exist are skipped with a warning
  • CRDs cannot be templated
  • There is no support for upgrading or deleting CRDs via Helm

This is not an oversight. It is a deliberate design decision: CRD upgrades can corrupt or destroy existing custom resource data if schema changes are incompatible, and Helm cannot safely reason about that risk. The maintainers have explicitly chosen to keep CRD management out of Helm’s scope.

What SSA does change for custom resource instances: With SSA enabled, Helm now properly merges CR instances (not CRD definitions) during upgrades. In Helm 3, CR fields set outside Helm were frequently overwritten. With SSA, the API server handles field merging based on the CRD’s structural schema. This is a meaningful operational improvement for teams running Istio, Prometheus Operator, and cert-manager alongside Helm-managed workloads.

Best practice for operator-heavy environments: Keep the two-chart pattern. One chart for CRD definitions (applied manually or managed by the operator’s own tooling), one chart for CR instances. Do not mix CRD definitions and CR instances in the same chart.

Step-by-Step Migration Path

Five phases, with explicit gate checks and rollback points at each phase boundary.

graph TD
    P1["Phase 1: Audit"] --> P1a["helm list -A - inventory all releases"]
    P1a --> P1b["Find post-renderer and plugin usage in CI/CD configs"]
    P1b --> P1c["Identify CRD-deploying charts and operator workloads"]
    P1c --> P2["Phase 2: Update RBAC"]
    P2 --> P2a["Add 'watch' verb to Helm service account ClusterRole"]
    P2a --> P2b["Validate: kubectl auth can-i watch deployments --as=..."]
    P2b --> G1{RBAC validated?}
    G1 -->|no| RB1["Fix permissions - do not proceed"]
    G1 -->|yes| P3["Phase 3: Test in Staging"]
    P3 --> P3a["Install Helm 4 as separate binary at /usr/local/bin/helm4"]
    P3a --> P3b["Run: helm4 upgrade --dry-run=server on all releases"]
    P3b --> P3c["Test actual upgrades in staging with --wait"]
    P3c --> P3d["Validate post-renderer and plugin workflows"]
    P3d --> G2{All staging tests pass?}
    G2 -->|no| RB2["Fix issues - keep Helm 3 for production"]
    G2 -->|yes| P4["Phase 4: Upgrade CLI in CI/CD"]
    P4 --> P4a["Replace helm binary in pipeline and tooling"]
    P4a --> P4b["Rename --atomic to --rollback-on-failure if hardcoded"]
    P4b --> P4c["Run first production upgrade and monitor release health"]
    P4c --> G3{Releases healthy?}
    G3 -->|no| RB3["Roll back: reinstall Helm 3 binary - releases stay intact"]
    G3 -->|yes| P5["Phase 5: SSA Migration - optional, deliberate"]
    P5 --> P5a["Per-release: helm upgrade --server-side=true --dry-run=server"]
    P5a --> P5b["Resolve ownership conflicts - --force-conflicts if needed"]
    P5b --> P5c["Monitor for CR timeout issues under kstatus --wait"]

SSA migration is a separate step from CLI migration. You can run Helm 4 against all existing releases indefinitely with client-side apply. Migrate to SSA one release at a time on your own schedule.

Phase 1: Audit Your Environment

# Inventory all releases across all namespaces
helm list -A

# Check full state of a specific release
helm get all <release-name> -n <namespace>

# Find post-renderer usage in CI/CD configs (adjust paths to your repo layout)
grep -r "post-renderer" .github/ .gitlab-ci.yml Makefile scripts/

Flag every release that deploys CRDs or custom resources. These are the highest-risk releases for kstatus wait timeouts.

Phase 2: Update RBAC

Add watch to every resource type your Helm service account manages before installing Helm 4:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: helm-deployer
rules:
  - apiGroups: ["", "apps", "batch"]
    resources: ["*"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

Phase 3: Test in Staging

Install Helm 4 alongside Helm 3 as a separate binary:

# Download and install Helm 4 with a distinct name
curl -fsSL https://get.helm.sh/helm-v4.1.4-linux-amd64.tar.gz | tar xz
sudo mv linux-amd64/helm /usr/local/bin/helm4

# Verify version
helm4 version

# Server-side dry-run all releases - catches SSA conflicts before they bite
helm4 upgrade <release> <chart> -n <namespace> --dry-run=server

Phase 4: Upgrade CLI in CI/CD

Replace the binary in your pipeline. If you had --atomic hardcoded in scripts, update it (the old flag still works but logs deprecation warnings on every run):

# Old Helm 3 pattern
helm upgrade myrelease ./mychart --atomic --timeout 5m

# Helm 4 equivalent
helm upgrade myrelease ./mychart --rollback-on-failure --timeout 5m

Phase 5: SSA Migration (On Your Schedule)

Once the Helm 4 binary manages all releases, SSA migration is a separate deliberate step. Migrate one release at a time, starting with non-critical workloads:

# Migrate a release to SSA explicitly
helm upgrade myrelease ./mychart -n production --server-side=true

# Preview ownership changes first
helm upgrade myrelease ./mychart -n production --server-side=true --dry-run=server

# Resolve conflicts if needed
helm upgrade myrelease ./mychart -n production --server-side=true --force-conflicts

Rollback Strategy: What to Do If Migration Fails

Helm 4 and Helm 3 coexist cleanly. The release metadata format is backward compatible: releases created or upgraded by either version are manageable by the other. If you need to revert after upgrading the binary:

  1. Keep the Helm 3 binary available at a versioned path (/usr/local/bin/helm3)
  2. Restore it to PATH by pointing your symlink or CI variable back to the Helm 3 binary
  3. Run helm list -A to confirm all releases are visible and healthy
  4. Continue managing releases from their last-known state; the binary swap has no effect on release history

One caveat: If you already migrated a release to SSA with --server-side=true, rolling back the binary does not revert the apply method stored in release metadata. Helm 3 will manage that release with client-side apply regardless, which is generally safe but leaves field manager state in the Kubernetes API server in an inconsistent condition. Avoid mixing SSA-migrated releases with Helm 3 management in production if you can.

GitOps Considerations: ArgoCD and Flux

Helm 4’s SSA changes do not affect ArgoCD in the same way they affect direct CLI usage.

ArgoCD does not call helm install or helm upgrade. It uses helm template to render manifests and then applies them with its own sync engine. Helm 4’s SSA default is irrelevant to ArgoCD-managed charts. ArgoCD has its own SSA support via the ServerSideApply: true sync option on the Application resource, configured independently of Helm. The exact interaction between ArgoCD’s SSA and Helm 4-rendered manifests has limited primary source documentation at this time. Test explicitly in a staging ArgoCD instance before enabling both simultaneously.

Flux uses the Helm SDK natively via HelmController and the HelmRelease CRD. This means closer alignment with Helm 4 behavior since Flux is calling the same library code rather than templating output. Flux users should watch for HelmController updates that track Helm 4 SDK compatibility as the ecosystem stabilizes.

For both tools: the kstatus --wait changes are CLI-specific. GitOps controllers implement their own readiness logic independent of Helm’s --wait flag and are not directly affected by that change.

Frequently Asked Questions

Can I run Helm 3 and Helm 4 side by side during migration?

Yes. Helm 3 and Helm 4 are separate binaries. Install Helm 4 as helm4 or in an alternate PATH location and test against staging clusters while keeping Helm 3 for production. Release metadata is backward compatible, so both binaries can manage the same releases without conflict. The HIP-0012 development process document confirms this compatibility guarantee explicitly.

Will my existing Helm 3 releases break when I upgrade to Helm 4?

No. Helm 4 reads the prior apply method from release metadata and latches to client-side apply for any release created or last upgraded by Helm 3. Your existing releases behave exactly as they did. SSA only applies to new helm install operations or when you explicitly pass --server-side=true on an upgrade. This latching behavior is the core design of HIP-0023 and is intentional.

Do I need to update my RBAC roles before upgrading to Helm 4?

Yes, if you use --wait. Helm 4’s kstatus watcher requires watch permission on resources in addition to the list permission that Helm 3 required. Without the watch verb, --wait operations fail with permission errors immediately after the binary upgrade, before any chart changes are applied. Update your ClusterRole or Role and validate with kubectl auth can-i before swapping the binary in any environment that uses --wait. See HIP-0022 for the full RBAC specification.

What happens to my custom Helm 3 plugins after upgrading?

Existing Helm 3 subprocess-style plugins continue to work in Helm 4. They are deprecated but will be supported through the entire lifecycle of Helm 4, with removal planned for Helm 5. The breaking change is for post-renderers: any workflow using --post-renderer <executable> must be migrated to a plugin with a plugin.yaml manifest. The subprocess runtime in that manifest provides identical behavior to the Helm 3 post-renderer approach. See HIP-0026 for the full plugin specification and manifest format.

How do I test if my charts are compatible with Helm 4 before migrating?

Use server-side dry-run: helm upgrade <release> <chart> --dry-run=server. Do not use --dry-run=client since it does not simulate SSA behavior and will not surface field ownership conflicts. For thorough testing, install Helm 4 alongside Helm 3 on a staging cluster and run your full CI/CD pipeline against it. Pay particular attention to releases that use --wait and those that deploy custom resources: these are the areas where Helm 4 behavior diverges most from Helm 3, and staging validation is the only way to confirm compatibility before your production cutover.