Inside Helm - How Charts, Releases, and State Work in Kubernetes
Hey my DevOps queens (and kings, too)! 👑
If you’ve ever deployed an app to Kubernetes and thought, “Wait… what actually happens after I type helm install?” — this one’s for you.
Helm makes Kubernetes deployments look easy. But behind the scenes, it’s doing a lot more than just rendering YAML files. It’s a stateful release manager that tracks every change, stores versioned state inside your cluster, and handles upgrades intelligently — no external database required.
Today, let’s pull back the curtain and see how Helm actually works: how charts are structured, where your release data lives, and how Helm keeps everything consistent and versioned like a pro.
What’s Inside a Helm Chart
A Helm chart is a package that describes how your app should be deployed to Kubernetes. It’s structured, predictable, and — if you do it right — beautifully reusable.
my-chart/ Chart.yaml # metadata (name, version, dependencies, etc.) values.yaml # default configuration values templates/ # Go templates for Kubernetes manifests _helpers.tpl # reusable named templates and functions charts/ # subcharts (dependencies) files/ # arbitrary files used in templates crds/ # CustomResourceDefinitions (installed first) .helmignore # like .gitignore for packagingThink of it as your app blueprint — flexible through values.yaml and easily overridden at deploy time.
Pro tip: CRDs in the crds/ directory are:
- installed first,
- not templated,
- and never upgraded or deleted — this protects existing Custom Resources and data.
What Actually Happens When You Run helm install
Let’s break it down. When you run helm install, Helm doesn’t just “apply some YAML.” It follows a clear sequence:
- Loads the chart (local or remote).
- Merges values from multiple sources:
- base
values.yaml; - any additional files:
-f base.yaml -f prod.yaml(left-to-right; later overrides earlier); - inline flags:
--set,--set-string,--set-file,--set-json(these always take precedence).
- base
- Renders Go templates into complete Kubernetes manifests.
- Applies them to the cluster.
On a clean install, Helm creates the resources.
If resources already exist and contain the correct Helm annotations (meta.helm.sh/*, app.kubernetes.io/managed-by=Helm), Helm will adopt them. Otherwise, you’ll see:
Error: invalid ownership metadataDuring upgrades or rollbacks, Helm performs a three-way strategic merge — comparing the previous manifest, the new manifest, and the current cluster state — handled client-side through client-go.
Where Helm Stores Release State
Every Helm release is stored inside your cluster. No external DBs, no magic.
By default, Helm saves release info in a Kubernetes Secret, though you can switch to a ConfigMap or SQL driver (PostgreSQL).
Default object structure:
kind: Secrettype: helm.sh/release.v1metadata: name: sh.helm.release.v1.<release>.v<revision> namespace: <release-namespace>The .data.release field contains a base64-encoded, gzip-compressed blob with:
- the chart archive,
- rendered manifests,
- merged values,
- hooks,
- and release metadata (revision, timestamps, status).
Because Secret data is base64-encoded by Kubernetes — and the release payload also includes encoded sections — the result is effectively nested encoding.
Heads up: etcd enforces a ~1 MiB limit for Secret data. If you hit:
Too long: must have at most 1048576 charactersswitch to SQL storage:
export HELM_DRIVER=sqlexport HELM_DRIVER_SQL_CONNECTION_STRING=postgresql://user:pass@host/dbLabels and Annotations
Helm labels everything it manages to track ownership precisely.
Added automatically:
app.kubernetes.io/managed-by=Helmmeta.helm.sh/release-name=<release-name>meta.helm.sh/release-namespace=<namespace>Best-practice labels (usually added by chart templates):
app.kubernetes.io/instance=<release-name>helm.sh/chart=<chart-name>-<version>If these don’t match during an upgrade, Helm will refuse ownership — hence the classic invalid ownership metadata error.
What Is a Helm Release?
A release is one installed instance of a chart with a specific set of values (in a namespace).
Each change — install, upgrade, rollback — becomes a new revision stored right in your cluster. No external database. No API extension. Just Kubernetes objects doing their job.
The Three-Way Merge Explained
When you upgrade, Helm compares:
- the previous rendered manifest,
- the new manifest (after your changes),
- the current live resources.
Then it calculates a three-way strategic merge patch and applies only what changed — reducing disruption and preserving manual tweaks where possible.
NOTEThis logic runs client-side, not via Server-Side Apply. For CRDs or unstructured resources, Kubernetes uses JSON merge-patch, which can behave slightly differently.
Namespaces: Release vs Target
Helm separates namespaces into two categories:
- Release namespace — where Helm stores its Secret or ConfigMap.
- Target namespaces — where chart resources are deployed (can be multiple).
--create-namespaceonly creates the release namespace (the one you pass with--namespace).
If your chart deploys across namespaces, create them manually or template them inside your chart.
Tip: Avoid hardcoding metadata.namespace unless you really need to — otherwise helm template | kubectl apply might push resources to default.
Storage Drivers
Helm supports multiple backends for storing release state:
export HELM_DRIVER=secret # default (encoded, secure)export HELM_DRIVER=configmap # readable, not encryptedexport HELM_DRIVER=sql # PostgreSQL – best for large releasesexport HELM_DRIVER=memory # ephemeral, for tests onlyManaging Release History
Helm keeps all revisions unless you limit it. You can cap history like this:
helm upgrade --history-max 10 ...export HELM_MAX_HISTORY=10Explore release history anytime:
helm history <release>helm get values <release>helm get manifest <release>TL;DR
Helm isn’t magic — it’s clean engineering. Behind every helm install, it:
- loads, merges, and renders templates,
- stores state inside your cluster,
- tags resources for ownership tracking,
- applies three-way strategic merges on upgrade,
- manages revisions — all with zero external dependencies.
Once you understand how Helm really works, it stops feeling mysterious and starts feeling like your favorite DevOps assistant.
Quick Debug Tip
helm ls -Akubectl get secrets -A -l "owner=helm" -l "status=deployed"Perfect for quick audits or just checking what Helm has been managing lately.
💬 Have a weird Helm issue or a release gone rogue?
Drop it in the comments — I love solving real DevOps mysteries together 💖
Tools I Personally Trust
If you’re building things, breaking things, or just trying to keep your digital life a little calmer - these are tools I actually use every day:
🛸 Proton VPN (60% off link) - my invisible cloak online. It secures your Wi-Fi, hides your IP, and blocks trackers - even on the sketchiest café Wi-Fi.
🔑 Proton Pass (50% off link) - my password vault. On-device encryption for logins, 2FA, and notes - all mine and only mine.
🦑 GitKraken Pro (50% off link) - my visual Git sidekick. Gorgeous commit graph, painless merges, and fewer “what just broke?” moments.
💖 These links give you sweet discounts - and help support DevOps.Pink at no extra cost. Thanks a ton!
Social Channels
🎬 YouTube
🐦 X (Twitter)
🎨 Instagram
🐘 Mastodon
🧵 Threads
🎸 Facebook
🦋 Bluesky
🎥 TikTok
💻 LinkedIn
📣 daily.dev Squad
✈️ Telegram
🐈 GitHub
Community of IT Experts
👾 Discord
Refill My Coffee Supplies
💖 PayPal
🏆 Patreon
🥤 BuyMeaCoffee
🍪 Ko-fi
⚡ Telegram Boost
Is this content AI-generated?
Absolutely not! Every article is written by me, driven by a genuine passion for Docker and backed by decades of experience in IT. I do use AI tools to polish grammar and enhance clarity, but the ideas, strategies, and technical insights are entirely my own. While this might occasionally trigger AI detection tools, rest assured—the knowledge and experience behind the content are 100% real and personal.