skip to content

What does Helm record in a chart's Chart.lock, and what are its digest and generated fields?

level: juniorimportance: should knowfreq 48%

answer

  1. Ranges live in one file, results in another
  2. Three top-level keys, one list plus two scalars
  3. One concrete version per subchart entry
  4. The hash covers declarations, not archives
  5. digest catches an edited Chart.yaml

basics

~10 s

Chart.lock records the exact subchart versions Helm resolved from the ranges in Chart.yaml, each with its name, repository and version. The digest field hashes those dependency declarations; generated is the timestamp of the resolution.

solid answer

~50 s

`Chart.lock` is the resolved form of the `dependencies:` block in `Chart.yaml`. Where `Chart.yaml` may say `version: "^2.14.0"`, the lock records the one version Helm actually picked - `2.14.3` - together with that dependency's `name` and the `repository` it came from, one entry per subchart. Two extra top-level fields sit beside the list. `digest` is a hash over the dependency declarations, so Helm can tell that someone edited `Chart.yaml`'s dependencies after the lock was written; `helm dependency build` refuses to run on a lock whose digest no longer matches rather than quietly installing stale pins. `generated` is the timestamp of the resolution, useful only as an age signal. Note what the digest is *not*: it does not hash the downloaded tarballs, so the lock says nothing about the bytes of a subchart, only which version was chosen.

code

yaml · 5 lines
yaml
# Chart.yaml - the range the author is willing to accept
dependencies:
  - name: invoice-operator
    version: "^2.14.0"
    repository: "https://charts.example.com"

go deeper

for a junior

Be ready to say plainly that Chart.yaml holds ranges and Chart.lock holds the one version each range resolved to, and that you commit the lock rather than editing it by hand.

for a middle

Explain the three top-level keys and, above all, what the digest actually hashes: the dependency declarations, so Helm can detect that Chart.yaml changed after the lock was written.

for a senior

Show how you use the lock in practice - a pin refresh becomes a reviewable diff, a stale generated timestamp is a signal, and you can say what the lock does not guarantee about the fetched bytes.

for a principal

Own the policy question: whether ranges are permitted at all in your charts, who is allowed to regenerate a lock, and how pin refreshes get reviewed rather than happening as a side effect of a build.

## What the file is A Helm chart declares its subcharts in `Chart.yaml` under a `dependencies:` list. Each entry has a `name`, a `version` that is normally a SemVer *range*, and a `repository` - a classic chart repository URL or an `oci://` reference. A range is deliberately loose: `^2.14.0` means "any 2.x at or above 2.14.0". Loose is convenient for the author and useless for a deploy, because the same source tree would install different subcharts on different days. `Chart.lock` is the file that closes that gap. It sits next to `Chart.yaml` at the chart root and is written by `helm dependency update`. It is not hand-authored; you commit it, you do not edit it. ## What is inside The file has three top-level keys: ```yaml dependencies: - name: invoice-operator repository: https://charts.example.com version: 2.14.3 digest: sha256:9f2c1d4a7b3e08c5d61f8a2b04e7c93d5a1b6f8027e4c39d0a5b7e1c8d4f2a63 generated: "2026-08-19T11:42:07.318204Z" ``` **`dependencies`** mirrors the `Chart.yaml` list, but every `version` is now a single concrete version rather than a range, and the `repository` is the one Helm actually resolved against. Optional declaration fields such as `alias`, `condition` and `tags` are not what the lock is about - the lock's job is resolution, so it carries the identity of each resolved chart. If a subchart itself has dependencies, those are resolved into that subchart's own lock, not flattened into yours. **`digest`** is the field most candidates get wrong. It is a hash computed over the dependency *declarations*, not over the downloaded archives. Its only purpose is drift detection between two files that are both in your repository: `Chart.yaml` and `Chart.lock`. When you add a dependency, delete one, or change a range and then commit only `Chart.yaml`, the recomputed hash no longer matches the stored `digest`, and Helm reports that `Chart.lock` is out of sync with `Chart.yaml`. That is a correctness guard on the pipeline: a build that installed the old pins while `Chart.yaml` asked for something else would be silently wrong. It is emphatically **not** an integrity check on the subchart bytes - if a chart repository served different content under version `2.14.3` tomorrow, nothing in the lock would notice. Content verification in Helm is a separate, opt-in mechanism based on a signed `.prov` file. **`generated`** is an RFC3339 timestamp of when the resolution ran. Helm does not act on it; humans do. A `generated` date from fourteen months ago on a chart whose ranges are all carets tells you the pins have never been refreshed, and that the ranges in `Chart.yaml` are decorative. ## Why the file exists at all The lock separates two questions that interviewers like to see separated: 1. *What am I willing to accept?* - answered by the range in `Chart.yaml`, an authoring decision. 2. *What did I actually get?* - answered by `Chart.lock`, a resolution result. Once the second question has an answer stored in version control, a pipeline can install a chart without ever asking the first question again. That is what `helm dependency build` does: it reads `Chart.lock` and fetches exactly those versions. `helm dependency update` is the opposite - it re-asks question one, re-resolves the ranges, and rewrites the lock plus the `charts/` directory. Refreshing a pin therefore becomes a reviewable commit: the diff of `Chart.lock` shows `2.14.3` becoming `2.15.1`, and a reviewer can ask why. ## Practical consequences - **Commit it.** A chart repository with `Chart.yaml` ranges and no `Chart.lock` has no pins at all; every build resolves fresh. - **A lock does not pin a top-level chart.** It pins that chart's *dependencies*. What you install at the top - `helm install ... --version` or a packaged `.tgz` - is a separate decision the lock has no opinion on. - **A lock does not pin what the subchart deploys.** Container image references live in values, and to Helm they are opaque strings. - **The digest is over declarations.** So a mismatch means "a human edited `Chart.yaml`", not "a tarball was tampered with". ## Reading a lock in review The lock is the most useful file in a chart's diff. A pull request that changes only `Chart.lock` is a dependency bump and nothing else, and the single changed line - `version: 2.14.3` becoming `version: 2.15.1` - is the entire question a reviewer has to answer. A pull request that changes `Chart.yaml`'s ranges *and* `Chart.lock` is an author widening or narrowing what is acceptable and re-resolving in the same breath; those are two decisions and are worth separating. A pull request that changes `Chart.yaml`'s dependencies and leaves the lock alone is broken, and the digest is what makes it fail loudly rather than deploy something stale. Repositories that never show a `Chart.lock` diff are the ones to worry about. Either the chart genuinely has no dependencies, or the lock is not committed and every build resolves afresh - in which case the ranges in `Chart.yaml` are the only thing standing between you and whatever upstream published last night. Being precise about the digest is usually the difference between a candidate who has read the file and one who has only heard the word "lockfile".

  • If someone adds a dependency to Chart.yaml but commits no new Chart.lock, what does the next locked install do?
    It fails rather than proceeding. The digest stored in `Chart.lock` was computed over the old declarations, so it no longer matches what `Chart.yaml` now says, and `helm dependency build` reports the lock as out of sync. The fix is to run `helm dependency update` and commit the regenerated `Chart.lock` alongside the `Chart.yaml` change, so the pin and the declaration land in the same reviewable commit.
  • Does Chart.lock protect you if a chart repository republishes different content under a version you already pinned?
    No. The lock stores a name, a repository and a version string; it carries no hash of the subchart archive, and its `digest` covers only the declarations in `Chart.yaml`. If the same version is re-uploaded with different content, a locked build fetches the new bytes without complaint. Content trust in Helm is a separate, opt-in path using a signed provenance file, and immutability is a property you have to demand from wherever the chart is published.
  • Is there a lock entry for a subchart's own subcharts?
    Not in yours. Resolution is per chart: your `Chart.lock` lists the dependencies declared in your `Chart.yaml`. A dependency that itself declares dependencies resolves them into its own lock and ships them inside its packaged archive, so by the time you fetch it the nested tree is already fixed. That is why a deep dependency bump usually arrives as a new version of the direct subchart rather than as a line in your lock.

Chart.yaml's ranges are the shopping list ("any 2.x milk"); Chart.lock is the receipt naming exactly which carton came home, and the digest is a checksum of the list itself so you notice when someone rewrote the list after the shop.

saying these in an interview costs you the question

  • Claims the digest is a checksum of the downloaded subchart tarballs
  • Says Chart.lock is hand-edited to change a pinned version
  • Thinks Chart.lock stores the range rather than a resolved version
  • Assumes the lock pins the top-level chart's own version
  • Believes generated is read by Helm to expire the lock
  • Says a lock makes subchart content immutable

context