skip to content

Where does Helm store the record of a release, and what is inside it?

level: juniorimportance: must knowfreq 72%

answer

  1. State lives in the cluster, not locally
  2. One object per revision, in the release namespace
  3. A Secret by default, with its own type
  4. sh.helm.release.v1.<name>.v<rev>
  5. Gzipped JSON: chart, values, manifest, status

basics

~20 s

Helm writes one Secret per release revision into the release's own namespace, named sh.helm.release.v1.<name>.v<rev> with type helm.sh/release.v1. It carries the chart, the values supplied, the rendered manifest and the status, as JSON that is gzipped and base64-encoded.

solid answer

~50 s

Helm keeps no local state file for a release: every revision is written into the cluster, in the release's own namespace, as a Secret named `sh.helm.release.v1.<name>.v<rev>` with the type `helm.sh/release.v1`. Its single `release` key holds the whole release object — the chart itself (every file in it, including `values.yaml` and the templates), the values the caller supplied, the manifest Helm rendered and applied, the hooks and notes, plus status and timestamps — JSON-encoded, gzipped, then base64-encoded, and base64-encoded once more by the Secret's own `data` encoding. Because the record is self-contained, `helm rollback` and `helm get` need nothing from the original chart source. The namespace is part of the identity: the same release name in two namespaces is two unrelated releases. Labels such as `owner=helm`, `name`, `status` and `version` are what `helm list` selects on.

code

bash · 2 lines
bash
kubectl -n telemetry get secret sh.helm.release.v1.gateway.v7 \
  -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | head -c 400

go deeper

for a junior

Be ready to say, without hesitating, that release state lives in the cluster as a Secret per revision in the release's namespace, and that its name follows sh.helm.release.v1.<name>.v<rev>. Knowing there is no local state file is half the point of the question.

for a middle

Explain the payload: the chart, the supplied values, the rendered manifest and the status, JSON-encoded, gzipped and base64-encoded twice by the time it is in the Secret's data. Connect that to why rollback needs nothing from the chart repository.

for a senior

Draw out the operational consequences: values passed at install time are readable by anyone with Secret read access in that namespace, every revision carries a full chart copy so records add up, and losing the records orphans workloads rather than deleting them.

for a principal

Own the position that this record is derived state, not the source of truth. Argue where the real desired state lives, what your recovery story is when a namespace's records are lost, and who is allowed to read Secrets in namespaces where charts receive credentials as values.

### One object per revision A Helm release is not a Kubernetes object. Nothing in the cluster natively knows that a Deployment, a Service and a ServiceAccount were installed together by one `helm install`. Helm invents that grouping and persists it itself, and the place it persists it is an ordinary Secret in the release's namespace. The naming is fixed and mechanical: `sh.helm.release.v1.<release-name>.v<revision>`. A release called `gateway` that has been upgraded three times has four objects — `...gateway.v1` through `...gateway.v4` — sitting side by side. They are never rewritten in place; an upgrade creates the next one. The Secret carries the type `helm.sh/release.v1`, which is how you can enumerate every Helm-managed release in a cluster without guessing at names. The namespace is part of the release's identity, not decoration. `gateway` in `telemetry` and `gateway` in `telemetry-staging` are two releases with two independent revision chains, because their record Secrets live in different namespaces. That is also why `helm list` shows one namespace at a time unless you ask for all of them, and why the namespace a command runs against is as load-bearing as the release name. ### What the record actually contains The Secret has one data key, `release`. Decoding it yields a JSON document with roughly these parts: - **`chart`** — the chart itself, file by file: `Chart.yaml`, `values.yaml`, everything under `templates/`, the metadata, and any subcharts that were resolved at install time. The record embeds the chart; it does not point at a repository. - **`config`** — the values the caller supplied on top of the chart's defaults (`-f` files and `--set` flags, merged). - **`manifest`** — the YAML Helm actually rendered and sent to the API server for this revision. - **`hooks`** — the hook manifests, kept apart from the ordinary manifest because they are applied on their own schedule. - **`info`** — status (`deployed`, `superseded`, `failed`, `pending-upgrade` and so on), first-deployed and last-deployed timestamps, the human description, and the notes rendered from `NOTES.txt`. That list explains a lot of Helm's behaviour. `helm rollback 3` works offline, with the chart repository unreachable and the chart directory deleted, because revision 3's record still holds the chart, the values and the rendered manifest. `helm get values` can tell you exactly what someone passed six months ago. And a chart that vendors large files makes every single revision record large, because the chart is copied into each one. One consequence deserves naming: whatever values were supplied are stored in that record. If someone passed a database password through `--set`, it is sitting in the release Secret, readable by anyone who can read Secrets in that namespace, regardless of whether the chart ever rendered it into a Kubernetes Secret of its own. ### The encoding, and why it looks doubly encoded The release object is marshalled to JSON, gzipped, and base64-encoded. That base64 string is then stored as a Secret value — and Kubernetes base64-encodes `data` values itself when it serialises the object. So reading it by hand means decoding twice and then decompressing: The gzip step matters more than it looks. Rendered Kubernetes YAML is extremely repetitive and compresses hard, which is the only reason a chart producing several megabytes of manifest fits in a Secret at all. It also means the practical size budget is a compressed budget, and you cannot predict it by measuring the rendered output. ### Labels, and how Helm finds anything Helm does not scan every Secret. The record carries labels — `owner=helm`, `name=<release>`, `status=<status>`, `version=<revision>` — and Helm queries by label selector. That is what makes `helm list` fast and what makes the status filters work. It is also why a cleanup tool that strips labels from Secrets can make a release vanish from `helm list` while the record object is still sitting there. ### What this model buys and costs The upside is that release state travels with the cluster and with the namespace. Clone the namespace's Secrets and you have carried the release history; back up the cluster and the history is in the backup; grant someone namespace access and Helm works for them with no extra bootstrap. There is no server-side Helm component and no shared state store to operate. The cost is that the state is only as durable, only as private, and only as size-limited as a Secret in that namespace. Delete the records and Helm forgets the release entirely — the workloads keep running, untouched and now unmanaged, because nothing about the record is what keeps them alive. That asymmetry is worth internalising: the record is Helm's bookkeeping, not the deployment.

  • Someone deletes every sh.helm.release.v1 Secret for a release. What happens to the running workloads?
    Nothing happens to them. The Deployments, Services and everything else keep running exactly as they were — the record is Helm's bookkeeping, not the thing keeping objects alive. What breaks is Helm: the release disappears from `helm list`, history and rollback are gone, and the next `helm upgrade` fails because there is no release to upgrade. Re-running `helm upgrade --install` then collides with the existing objects, because they still carry the ownership metadata from the release Helm no longer remembers.
  • The same release name exists in two namespaces. Are those the same release?
    No. The record Secrets live in the release's namespace, so `gateway` in `telemetry` and `gateway` in `telemetry-staging` have separate revision chains, separate histories and separate values. Uninstalling one does not touch the other. This is also the reason a Helm command's namespace is not a convenience flag: run an upgrade against the wrong namespace and Helm sees no existing record, so with `--install` it will happily install a second, independent copy.
  • Why does helm rollback work when the chart repository is offline?
    Because the record for each revision embeds the chart itself, the values that were supplied, and the manifest that was rendered — it does not store a reference to be re-fetched. Rolling back reads the target revision's stored state and applies it, then writes a new revision on top. That is also why the records are not small: every revision carries its own full copy of the chart.

Think of it as a shipping manifest taped inside the crate rather than filed at head office: everything needed to re-create or reverse the shipment travels with it, in the same namespace as the goods.

saying these in an interview costs you the question

  • Says Helm keeps release state in a local file on the operator's machine
  • Says a server-side Helm component in kube-system holds the state
  • Thinks the record stores only a pointer to the chart in its repository
  • Believes deleting the release Secrets deletes the running workloads
  • Assumes the same release name is one release across all namespaces
  • Calls the stored data encrypted because it is base64-encoded

context