Chart.lock pins your subchart versions - what else must you pin so a chart renders identically in six months?
answer
- The lock answers exactly one question
- A version string is not a content hash
- Rendering has inputs the chart does not own
- Promote a file, not a commit range
- Two different meanings of reproducible
basics
~20 sChart.lock pins only which subchart versions are fetched, not their bytes. Reproducing a render also means fixing the parent chart artefact, the values files, image references, the Helm CLI version and the cluster API versions the render assumes.
solid answer
~50 sA lock answers exactly one question: which version of each declared subchart. Everything else that decides the output is outside it. It stores no content hash, so it trusts a repository to serve the same bytes under `2.14.3` forever. It says nothing about the parent chart's own version, about the values files and `--set` flags supplied at install, about the image references inside those values - opaque strings to Helm - or about the Helm CLI doing the rendering and the cluster API versions a template branches on. So the unit of reproducibility is not the source tree, it is a packaged chart `.tgz` plus the exact values that were applied. Package once, embedding the resolved subcharts; store the values file beside the artefact; pin images by digest; render on a fixed Helm version; and where a render is version-sensitive, fix `--kube-version` and `--api-versions` rather than letting the target cluster decide.
code
bash · 8 lines# Helm 4: byte-reproducible package from a locked source tree
helm dependency build ./invoice-worker
SOURCE_DATE_EPOCH=1755600000 helm package ./invoice-worker --destination dist/
# Render independently of whatever cluster is in the kubeconfig
helm template invoice-worker dist/invoice-worker-1.7.4.tgz \
-f values/prod-overrides.yaml \
--kube-version 1.31.4 > rendered/prod-1.7.4.yamlgo deeper
Know the boundary: Chart.lock pins which subchart versions get fetched, and nothing about the values you pass, the images those charts deploy, or the chart version you install at the top.
Explain each gap mechanically - no content hash in the lock, values supplied at install, image tags as opaque strings - and name packaging a .tgz as the way to make the fetch disappear.
Show how you would actually reproduce a six-month-old deploy: the promoted artefact, the values it ran with, the release record already stored in the namespace, and a fixed CLI version in CI.
Own the distinction between rebuild reproducibility and deploy reproducibility, choose the artefact-promotion model deliberately, and define who may refresh pins and how staleness is surfaced across the estate.
## The question behind the question Interviewers ask this because "we use a lockfile, so we are reproducible" is a common and comfortable half-truth. The lock's scope is narrow and precisely definable, and a lead is expected to know where it stops and what has to be built around it. ## What Chart.lock genuinely fixes For each dependency declared in `Chart.yaml`, the lock records a `name`, a `repository` and one resolved `version`, plus a `digest` over the declarations and a `generated` timestamp. A locked build (`helm dependency build`) therefore always asks for the same versions from the same places. That is real and it is worth having. ## Where it stops - five gaps **1. Version is not content.** The lock carries no hash of the fetched archive. If a chart repository re-publishes under an existing version, or an OCI tag is moved, a locked build silently gets different bytes. Immutability of published versions is a property you demand of your distribution, not something the lock enforces. Helm's own answer to *content* trust is a separate, opt-in provenance file signed at package time - opt-in is the operative word. **2. The parent chart is not pinned by its own lock.** The lock pins dependencies. What version of the umbrella chart you install is a separate decision made by whoever runs `helm install` or by a controller reading a chart reference. **3. Values are not in the lock.** Two installs of the identical chart with different `-f` files render different manifests. For a chart driven by, say, an eleven-value override file per environment, the values file is at least as load-bearing as the pins, and it is versioned somewhere else entirely - or not at all if it was assembled by a pipeline from variables. **4. Image references are opaque strings.** A subchart's `image.tag` is text as far as Helm is concerned. A chart pinned to subchart `2.14.3` that resolves an image tag can still deploy different software next month. If you want the workload pinned, the values must carry a digest, and that is your decision to make, not Helm's. **5. The renderer and the cluster participate.** The Helm CLI version supplies the template function set, and a template that branches on API availability reads what the target cluster reports. The same chart can therefore render differently against a different cluster. `helm template` accepts explicit `--kube-version` and `--api-versions` precisely so a render can be made independent of whatever cluster happens to be in your kubeconfig. ## What a lead actually builds **Promote an artefact, not a source tree.** `helm package` produces a `.tgz` that already contains the resolved subchart archives. That one file is a far stronger unit than "the repo at commit `a3f91c2` plus whatever the repositories serve today". Store it in an immutable location and promote the identical file from staging to production. In Helm 4, `helm package` honours `SOURCE_DATE_EPOCH`, so the tarball itself can be byte-identical across rebuilds - useful when you want to prove two builds of a source tree produced the same chart. **Version the values with the artefact.** A chart version plus a values file digest is the real deployment identity. Pipelines that assemble values from environment variables at deploy time destroy reproducibility more thoroughly than any unpinned range. **Fix the toolchain.** Pin the Helm CLI version in CI images. A minor CLI upgrade that adds template functions does not change your output, but a team that cannot say which CLI rendered a release cannot explain a diff either. **Keep the evidence.** Helm stores the rendered manifest and the supplied values in the release record for every revision, so a live cluster can already answer "what exactly did revision 17 apply". Treat that as the audit trail rather than trying to re-derive it later from source. **Refresh the pins on purpose.** The flip side of hard pinning is rot. A lock with a `generated` date from fourteen months ago means the ranges in `Chart.yaml` are fiction and nobody has looked at upstream since. The policy that works is: pins change only in a reviewed commit, but something scheduled proposes those commits, so staleness is visible rather than accidental. ## The framing to give Define the goal precisely, because "reproducible" hides two different asks. *Rebuild reproducibility* - the same source produces the same chart - is what the lock plus a deterministic package addresses. *Deploy reproducibility* - the same manifests reach the cluster - additionally needs the values, the image references and the renderer fixed. Say which one is being asked for, name the gaps that survive the lock, and choose an artefact-promotion model rather than trying to make a source tree behave like one. That is the answer that distinguishes a lead from a careful engineer.
- Chart.lock pins subchart 2.14.3, yet the same chart deployed different software this month. How?The lock pins charts, not workloads. A container image reference inside values is an ordinary string to Helm, so a subchart pinned at `2.14.3` that carries a mutable image tag deploys whatever that tag currently points at. The other possibility is that the repository re-published `2.14.3` with different content, which the lock cannot detect because it stores no content hash. Pin images by digest and demand immutable published chart versions.
- How do you stop hard-pinned charts from rotting once no build is allowed to bump them?Make refreshing a scheduled, reviewable event rather than a side effect. Something automated runs the resolution and opens a pull request whose diff is the changed `Chart.lock` lines; a human approves it, and the pipeline only ever materialises what was approved. The `generated` timestamp is the metric to watch across the estate - a chart whose lock has not moved in a year is unpinned in intent even though it is pinned in fact.
- Where does Helm already record what a given release actually applied?In the release record it stores in the namespace for each revision, which holds the rendered manifest and the values that produced it. That is the authoritative answer to "what did revision 17 apply", and it is stronger than re-deriving the render from source months later, because it captures the values and the output rather than the inputs you hope were used. Treat it as the audit trail and keep enough history to cover your investigation window.
saying these in an interview costs you the question
- Says a lockfile alone makes a deploy reproducible
- Assumes the lock verifies subchart content, not just version
- Forgets values files and --set are unversioned inputs
- Treats an image tag in values as pinned by the chart
- Ignores that the renderer and cluster affect output
- Pins everything hard with no plan for refreshing pins