Managing systemic change safely: migrating Helm 2 to Helm 3 via the helm-2to3 plugin, upgrading charts between major versions full of breaking changes, application upgrade strategies (rolling, blue-green, canary), and facing Kubernetes API deprecations and the Chart API v2.

After episode 26, where we covered troubleshooting and debugging — how to dissect installation failures, stuck releases, and advanced techniques like inspecting release secrets — in this episode we face a different kind of challenge: not recovering what's broken, but carrying out major changes in a controlled way. Platform migrations, major version upgrades, and facing deprecations are the riskiest moments in a system's life, and they almost always involve Helm.
Why is this topic important? Major changes have a unique character: they combine technical risk (breaking changes), operational risk (downtime if it fails), and process risk (who is responsible for what, and when). Companies already running in production can't choose to "never migrate" — Helm 2 must inevitably retire, chart major versions inevitably arrive, and deprecated Kubernetes APIs inevitably need replacing. There's only one thing to choose: a migration planned with the right method, or an emergency migration in the middle of an incident.
In this episode we dissect the Helm 2 to Helm 3 migration with the helm-2to3 plugin, major-version chart upgrade strategies, application upgrade strategies (rolling update, blue-green, canary) and how Helm interacts with them, then close with how to face deprecations — both Kubernetes APIs and the Chart API v1 vs v2.
If you join a long-running team, there's a chance remnants of Helm 2 still exist — or even releases still managed by Helm 2. Helm 2 is officially end-of-life (its last release was November 2020), and its Tiller is a serious security risk because it holds broad permissions in the cluster. Migration isn't a choice.
Helm 2 and Helm 3 differ fundamentally in three things that most affect operations:
apiVersion: v1 vs v2 in Chart.yaml, helm install --name removed (the release name becomes a positional argument), and the patch mechanism changed to three-way strategic merge.The consequence: there is no in-place upgrade. A Helm 2 release can't be "upgraded" to Helm 3 — it must be migrated. That's the job of the helm-2to3 plugin.
helm-2to3 is the official Helm community plugin that automates release and repository migration. Installation:
helm plugin install https://github.com/helm/helm-2to3
# Verify
helm plugin list
helm 2to3 helpThe plugin has three main subcommands — convert, move, and cleanup — and we'll run them in the correct order.
Step 1 — preparation. Back up all of Helm 2's state. The easiest and most thorough way: snapshot the release configmaps in the Tiller namespace. Also record the Helm 2 version and the list of running releases with helm2 list.
Step 2 — migrate releases. The convert command reads releases from ConfigMaps and rewrites them as Helm 3 Secrets:
# Connect to the same cluster as Tiller
helm 2to3 convert --tiller-namespace kube-system
# Migrate a specific release to another namespace
helm 2to3 convert api --tiller-namespace kube-system --release-namespace apiStep 3 — migrate repositories. Helm 2 repositories are stored as a ConfigMap named helm-repositories in the Tiller namespace. Move them into Helm 3 configuration:
helm 2to3 move config --tiller-namespace kube-system
helm repo listStep 4 — verify. Before cleaning anything up, test thoroughly. helm list --all-namespaces must show all the same releases as helm2 list. Run helm status <release> on several critical releases, compare helm get values with the values recorded before migration, and make sure applications keep running normally.
Step 5 — cleanup. Only after all releases are verified, clean up the Helm 2 remnants:
# Delete old release configmaps (IMPORTANT: check `--dry-run` first)
helm 2to3 cleanup --tiller-namespace kube-system --dry-run
# If confident, run without dry-run
helm 2to3 cleanup --tiller-namespace kube-system
# Finally, delete the Tiller deployment itself
kubectl -n kube-system delete deploy tiller-deploy
kubectl -n kube-system delete sa tillerThe cleanup order must not be reversed: delete the release state after a successful migration, not before. If a release is declared "missing" after convert, you can still go back to the Helm 2 ConfigMaps as long as they haven't been deleted. Clean up Tiller last, because it's the only component that still allows Helm 2 to work if the migration turns out to be flawed. And note: once Tiller is deleted, there's no going back — make sure all releases are in deployed condition on Helm 3 before executing this step.
Warning
Release migration changes how Helm views resource ownership. After convert, never manage the same release with Helm 2 and Helm 3 simultaneously. Choose one — and in practice, immediately use Helm 3 for all operations after verification, so there aren't two sources of truth for the same release.
Platform migration is only one kind of change. Every day, charts also change versions — and not all version increases are the same.
With semantic versioning (which we covered in episode 16), a major increase (2.x → 3.x) signals potentially breaking changes: changed values structure, different resource names, or incompatible behavior. Minor and patch increases are expected to be compatible — that's the contract that makes helm upgrade safe. But "expected" isn't "guaranteed": community charts often break semver by carrying big changes in minor versions. Because of that, always test, no matter the kind of version increase.
Almost all large charts publish upgrade notes — a UPGRADING.md file or a page in the repo — detailing the differences between major versions, especially values and resource changes needing manual handling (for example, values that must move from one key to another, or secrets that need recreating). A real example: upgrading the prometheus chart from an old version to a new one often moves configuration from server.* to prometheus.*; skipping the migration guide means rendering the chart with all values in the wrong place — and the application still "successfully" deploys with the wrong default configuration.
A major version upgrade requires an explicit plan. A flow you can use as a template:
1. Read the chart's UPGRADING.md / migration notes
2. Compare old values vs new schema/values
3. Render the new version locally, diff with the old
4. Test in staging with a production subset
5. Schedule the upgrade window + rollback plan
6. Monitor metrics & errors after go-liveNever upgrade a chart's major version directly in production without testing. The minimum practice: (1) helm template and diff locally; (2) a clean install of the new version in a dev/staging environment with values replicated from production; (3) an upgrade from the old to the new version in staging — not just a clean install, because the two are genuinely different (an upgrade faces already-existing resources); (4) only after all three pass, schedule it in production with a rollback plan prepared.
Chart upgrades often serve as the vehicle to upgrade an application — and that's where the classic deployment strategies come into play. Helm itself only applies changes to resources; strategies like rolling update, blue-green, and canary are policies for how that change is released to users.
The Kubernetes and Helm default strategy. The Deployment is updated gradually — maxUnavailable and maxSurge control how many Pods may be down and how many may exceed the target count. Its interaction with Helm is simple: one helm upgrade triggers one rolling update. helm status will wait until all new Pods are ready if --wait is used. When the application needs more control (for example, ensuring all new Pods are healthy before traffic flows in), add readinessProbe and minReadySeconds to the Deployment.
Two complete environments — blue (old) and green (new) — run simultaneously, then the Service is switched over entirely. In Helm practice, this is often represented as two releases (for example, api-blue and api-green), or one release with values swapping the label selector. Advantage: rollback as fast as switching traffic (zero-downtime), and the old version stays alive during the observation period. Disadvantage: duplicated resources (2× cost), and the Service's label values must be changed — this is the most error-prone point because a Deployment's selector can't be changed after creation.
The new version receives a small fraction of traffic (for example, 5%) and is ramped up gradually if metrics are healthy. This strategy is usually managed by tools like Argo Rollouts or Flagger — Helm itself has no built-in canary mechanism. In a GitOps architecture, the common pattern: the main release is managed by ArgoCD/Flux, and the canary is managed by Flagger, which creates temporary Deployments/Services via the same chart. When the canary passes, the full release is done via a Git commit.
An important point that's often missed: Helm doesn't understand deployment strategies — it only knows resources. Using any of the strategies above means you still manage Deployments/Services via the chart, while the release strategy is executed by another layer (Kubernetes controller, Argo Rollouts, Flagger). Design your chart so its labels and selectors are flexible enough to support these strategies — for example, with a selector.matchLabels.version parameter — before investing in an expensive strategy like blue-green.
The last part of this episode: surviving amid an ecosystem that keeps moving.
Kubernetes periodically removes old APIs. The most famous recent examples: extensions/v1beta1 for Ingress, Deployment, and DaemonSet (removed in Kubernetes 1.16+), and autoscaling/v2beta2 simplified to v2. If your chart still writes apiVersion: extensions/v1beta1, it will fail on new clusters with a clear error. How to guard against it: (1) monitor deprecation announcements in the Kubernetes release notes (an API's lifetime is usually 3 releases); (2) run periodic audits with tools like kubeconform or kubectl convert; (3) read Capabilities.KubeVersion in templates for conditional logic if needed.
A chart with apiVersion: v1 (Helm 2 format) can still be used by Helm 3, but doesn't get features like dependency declarations in Chart.yaml and values.schema.json. Migrating to v2 is usually simple:
# Before (v1)
apiVersion: v1
name: myapp
version: 1.0.0
# After (v2)
apiVersion: v2
name: myapp
version: 2.0.0
dependencies:
- name: postgresql
version: "15.x"
repository: https://charts.bitnami.com/bitnamiAfter changing apiVersion, move dependencies from requirements.yaml (if any) into the dependencies field, then run helm dependency update to generate a Chart.lock. Rendering and installation must still succeed — that's the fastest test.
When an API is removed, resources must be migrated. A real example: migrating a Deployment from extensions/v1beta1 to apps/v1. Because apps/v1 has long been a stable API, migration often just means changing apiVersion and adding the now-required selector field. Don't do this manually for hundreds of resources — use kubectl convert (the kubectl-convert plugin), then verify with a render and install in a staging cluster.
Every API migration must be tested in a cluster that no longer has the old API (or at least uses the new one). Best practice: maintain one "cutting-edge" environment that always uses the latest Kubernetes version, and run your chart suite there periodically. That way, deprecation issues are detected months before the grace period ends — not in the middle of a production cluster upgrade incident.
In this episode 27 we understood that migration and upgrade are disciplines of controlled change: migrating Helm 2 to Helm 3 via the helm-2to3 plugin with the strict convert → move → verify → cleanup sequence; major-version chart upgrades that demand reading migration guides, comparing values, and layered testing in staging; application upgrade strategies — rolling, blue-green, canary — and Helm's position as the applier of resources, not the governor of the release strategy; and how to face deprecations of both Kubernetes APIs and the Chart API v1 to v2.
The core takeaways:
The larger the system scale, the greater the demand for efficiency — slowly rendered charts, upgrades that wait a long time, and hundreds of releases flooding the cluster are no longer trivial matters. In the next episode, episode 28, we cover performance optimization and large scale: structuring efficient templates, shrinking chart size with .helmignore, tuning mass installations, managing hundreds of releases, and monitoring Helm health with helm-exporter and Prometheus. See you in episode 28!