887 words
4 minutes

Inside Helm - How Charts, Releases, and State Work in Kubernetes

Visual DX: 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 packaging

Think 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).
  • 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 metadata

During 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: Secret
type: helm.sh/release.v1
metadata:
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 characters

switch to SQL storage:

Terminal window
export HELM_DRIVER=sql
export HELM_DRIVER_SQL_CONNECTION_STRING=postgresql://user:pass@host/db

Labels and Annotations#

Helm labels everything it manages to track ownership precisely.

Added automatically:

app.kubernetes.io/managed-by=Helm
meta.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.

NOTE

This 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-namespace only 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:

Terminal window
export HELM_DRIVER=secret # default (encoded, secure)
export HELM_DRIVER=configmap # readable, not encrypted
export HELM_DRIVER=sql # PostgreSQL – best for large releases
export HELM_DRIVER=memory # ephemeral, for tests only

Managing Release History#

Helm keeps all revisions unless you limit it. You can cap history like this:

Terminal window
helm upgrade --history-max 10 ...
export HELM_MAX_HISTORY=10

Explore release history anytime:

Terminal window
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#

Terminal window
helm ls -A
kubectl get secrets -A -l "owner=helm" -l "status=deployed"

Perfect for quick audits or just checking what Helm has been managing lately.


VERDICT & AESTHETICS#

  • Visual Doctrine: Traditional DevRel creates noise. I engineer clarity, proving that deep infrastructure and an unapologetically pink aesthetic belong in the same boardroom. Deploy like a queen. Study the architecture on YouTube.
  • The Syndicate: Stop fighting your deployments alone. Gain access to zero-friction protocols, enterprise subsidies, and the DevOps Army. Enter the Discord Ecosystem.

Tatiana Mikhaleva

Principal Developer Advocate  ·  Docker Captain  ·  IBM Champion  ·  AWS Community Builder

Inside Helm - How Charts, Releases, and State Work in Kubernetes
https://devops.pink/inside-helm-how-charts-releases-and-state-work-in-kubernetes/
Architect
Tatiana Mikhaleva
Issued
2025-11-09
Protocol
CC BY-NC-SA 4.0