# Helm 3 to Helm 4 release fixture Tested on 2026-10-09 with Helm **3.22.0**, Helm **4.3.0**, and Kubernetes **1.37.0** in a single-node, Linux arm64 kind cluster (kind **0.33.0**). Both Helm binaries use Kubernetes client 1.37; choose compatible versions for your own disposable cluster. This fixture is not a production deployment. The chart serves a ConfigMap value through a small HTTP Deployment and ClusterIP Service. The BusyBox 1.37.0 multi-platform image index is pinned to the exact digest observed during the test. The chart has no secrets, persistent data, hooks, CRDs, external load balancer or application migration. ## Files - `chart/Chart.yaml` - `chart/values.yaml` - `chart/templates/workload.yaml` - `observed-results.json` — actual commands, outputs, image identity and HTTP checks; `$REPO` represents the local checkout. ## Prepare Download verified Helm binaries from the official releases at https://github.com/helm/helm/releases and verify their published checksums. Kubedex used `https://get.helm.sh/helm-v3.22.0-darwin-arm64.tar.gz` and the equivalent `v4.3.0` archive, comparing SHA-256 with each official `.sha256sum` file. This integrity check does not substitute for your software supply-chain policy. Select a **disposable** cluster. Set the paths below explicitly. These Bash commands do not use the default kubeconfig. The namespace must be absent before starting; never delete someone else's pre-existing namespace to make the example work. ```bash fixture_config=/absolute/path/to/disposable-kubeconfig fixture_context=your-disposable-context helm3=/absolute/path/to/helm-v3.22.0 helm4=/absolute/path/to/helm-v4.3.0 fixture_namespace=kubedex-helm-upgrade-fixture fixture_chart=./chart k=(kubectl --kubeconfig "$fixture_config" --context "$fixture_context") h=(--kubeconfig "$fixture_config" --kube-context "$fixture_context" --namespace "$fixture_namespace") "${k[@]}" get nodes "${k[@]}" create namespace "$fixture_namespace" ``` ## Install and upgrade ```bash "$helm3" install upgrade-demo "$fixture_chart" "${h[@]}" --wait --timeout 120s "$helm4" upgrade upgrade-demo "$fixture_chart" "${h[@]}" --set message=after-upgrade --wait --timeout 120s ``` Observed: revision 1 was `deployed` and served `before-upgrade`; revision 2 was `deployed` and served `after-upgrade`. To inspect the response after each step, run this in a separate terminal with the same variables and stop it before the next rollout: ```bash "${k[@]}" -n "$fixture_namespace" port-forward service/upgrade-demo 18080:8080 --address 127.0.0.1 ``` Then request `http://127.0.0.1:18080/`, for example with `curl --fail http://127.0.0.1:18080/`. A port-forward connection can end when its pod is replaced; reconnect after the rollout. Verify the response, not only the Helm status. ## Deliberately fail an upgrade, then recover ```bash "$helm4" upgrade upgrade-demo "$fixture_chart" "${h[@]}" --set message=failed-upgrade --set readinessPath=/missing --wait --timeout 20s ``` This command **must fail**. Kubedex observed exit 1 with `UPGRADE FAILED` and `context deadline exceeded`. The intentionally wrong readiness path prevents the new Deployment from becoming ready. The ConfigMap can still change during that failed upgrade; failure is not an automatic rollback. ```bash "$helm4" rollback upgrade-demo 2 "${h[@]}" --wait --timeout 120s "$helm4" history upgrade-demo "${h[@]}" ``` Observed: rollback succeeded, creating revision 4. Check that the served response converges to `after-upgrade`. In the first complete test, the API rollback had succeeded while the mounted ConfigMap still served `failed-upgrade`; the response converged after **52.9 seconds**. This is an observation of this environment, not a universal bound. Kubernetes projects ConfigMap changes asynchronously, and application recovery needs its own bounded check. ```bash "$helm4" rollback upgrade-demo 1 "${h[@]}" --wait --timeout 120s ``` Observed: revision 5 deployed, with response `before-upgrade`. This exercises Helm 4 rollback to a revision created by Helm 3. It does not prove safe rollback of databases, hooks, CRDs, plugins or external effects. Restoring the previous Helm binary is a separate workflow and was not tested. ## Cleanup Only after verifying that this is the namespace you created for the fixture: ```bash "${k[@]}" delete namespace "$fixture_namespace" --wait=true --timeout=120s ``` Kubedex's automated runner additionally checked the dedicated cluster name, asserted that the namespace did not already exist, verified actual HTTP responses with a 150-second convergence timeout, and removed only its own namespace. The full runner and original test evidence are in the source repository under `tests/guides/`.