skip to content

Would you commit a Helm chart's charts/ directory of vendored subchart tarballs, or only Chart.lock?

level: seniorimportance: should knowfreq 52%

answer

  1. One of the two files is not optional
  2. What the choice buys is independence, not pinning
  3. Binary blobs in a pull request
  4. Helm checks presence, not version, at install
  5. A packaged chart is already vendored

basics

~20 s

Always commit Chart.lock. Commit the charts/ tarballs only when you need a build that fetches nothing and survives an upstream repository disappearing, accepting binary blobs in review, repository growth, and drift that Helm will not detect at install time.

solid answer

~50 s

`Chart.lock` is not optional - without it the chart has no pins. The real decision is whether the resolved `.tgz` archives under `charts/` also go into version control. Vendoring buys one thing: a build that fetches nothing - no `helm repo add` in the runner, no registry login, no dependence on upstream still serving version `2.14.3` next year. For an air-gapped install, or a subchart from a third party you do not control, that matters. It costs review quality and repository size - tarballs are opaque in a diff - and it introduces drift the tool will not catch: Helm's install-time check on a source directory verifies only that a chart of each declared *name* is present in `charts/`, not that its version matches the lock. The common middle ground is lock-only in Git, with the release pipeline running `helm dependency build` against an internal mirror and packaging once - a packaged `.tgz` already carries its subcharts inside it.

code

bash · 5 lines
bash
# A packaged chart already carries its subcharts
helm dependency build ./invoice-worker
helm package ./invoice-worker
tar -tzf invoice-worker-1.7.4.tgz | grep '^invoice-worker/charts/'
# invoice-worker/charts/invoice-operator-2.14.3.tgz

go deeper

for a junior

Know that Chart.lock is always committed and that charts/ holds the fetched subchart tarballs, which many teams leave out of Git and regenerate with helm dependency build.

for a middle

Explain both sides concretely: vendoring removes the fetch and the dependence on upstream, at the cost of binary diffs and repository growth, and packaging already embeds charts/ inside the artefact.

for a senior

Demonstrate the operational detail - install-time checking is presence by name, not version, so committed tarballs can drift from the lock - and describe the mirror-plus-promote-one-artefact alternative.

for a principal

Own the org-wide call: whether an internal mirror exists at all, what artefact is promoted between environments, and which classes of chart are allowed to fetch anything at deploy time.

## What is actually being decided Three artefacts are in play for an umbrella chart - say a chart for an invoice-rendering worker that depends on an operator subchart shipping CustomResourceDefinitions: - `Chart.yaml` - the declared ranges. Always committed. - `Chart.lock` - the resolved versions. Always committed; a chart without it is unpinned no matter what the pipeline runs. - `charts/` - the fetched `.tgz` archives. This is the argument. ## The case for committing charts/ **The build fetches nothing.** With the archives present, `helm install`, `helm template` and `helm package` all work from the working tree alone. No repository has to be added to the runner, no registry login, no proxy allowlist. In an air-gapped environment that is not an optimisation, it is the only way it works. **Upstream cannot take it away.** A version can be yanked from a chart repository, a repository can move, a company can retire a public index. A vendored archive is immune. If your operator subchart comes from a vendor you do not control, this is the strongest argument on the list. **Byte-for-byte the same input.** A locked version resolves to whatever the repository serves under that version today; a vendored tarball is the exact bytes you tested against, because the lock stores no content hash of the archive. ## The case against **Review becomes blind.** A subchart bump lands as a changed binary blob. Nobody reads it, so the diff that used to be a single reviewable line in `Chart.lock` becomes noise attached to a line that is now easy to skim past. **The repository grows and never shrinks.** Every bump of a large subchart adds another compressed copy to history. Charts that vendor a handful of heavy dependencies grow into hundreds of megabytes of history over a couple of years. **Drift is undetected.** This is the subtle one. When Helm installs or renders a chart from a source directory, its dependency check is a *presence check by name*: it verifies that a chart called `invoice-operator` exists under `charts/` and errors with a "found in Chart.yaml, but missing in charts/ directory" style message when it does not. It does not compare the vendored archive's version against `Chart.lock`. So a `charts/invoice-operator-2.14.3.tgz` left behind while the lock has moved to `2.15.1` installs happily and quietly. Only `helm dependency build` or `update` reconciles the directory with the lock. **It duplicates what packaging already does.** `helm package` embeds the contents of `charts/` inside the produced `.tgz`. A packaged chart is therefore self-contained by construction - the vendoring you wanted at deploy time exists in the artefact whether or not the source tree carries it. `helm package` also fails when a declared subchart is missing from `charts/`, which is why `helm package -u` exists. ## The shape most teams land on Commit `Chart.lock`, `.gitignore` the `charts/` directory, and make the release pipeline responsible for materialising it: 1. `helm dependency build` against an **internal mirror** of the upstream repository, so availability no longer depends on a third party even though you did not vendor. 2. `helm package` once, producing a self-contained `.tgz`. 3. Promote that one artefact through environments. Nothing downstream ever resolves or fetches a subchart again. That gets most of vendoring's benefit - the deployable artefact fetches nothing - while keeping the source tree reviewable. The internal mirror is doing the work that vendoring would otherwise do: it is what makes "upstream disappeared" a non-event. When would you still vendor into Git? When there is no mirror and no artefact store to promote through; when the chart must build in an air-gapped clone; when regulation requires the exact deployed inputs to be in the same repository as the code; or when the subchart comes from a source you actively distrust to keep serving that version. ## What to say when asked State the non-negotiable first (`Chart.lock` is committed), name the one benefit vendoring actually delivers (a fetch-free build immune to upstream), then the three costs (opaque review, repository growth, drift Helm does not check), and finish with the packaged `.tgz` observation - that a released chart is already vendored, so the question is really about the *source tree*, not about the deployment. A candidate who mentions that install-time verification is a presence check by name, not a version check, has clearly operated this rather than read about it.

  • If charts/ is committed and drifts from Chart.lock, when does Helm tell you?
    Not at install time. Rendering or installing from a source directory only checks that a chart with each declared name is present under `charts/`; it does not compare the vendored archive's version against `Chart.lock`. A tarball left at `2.14.3` while the lock says `2.15.1` deploys silently. Only `helm dependency build` or `update` reconciles the directory with the lock, so CI should run `build` and fail if it changed anything on disk.
  • Does committing charts/ make the chart reproducible in a way Chart.lock alone does not?
    It fixes the bytes, which the lock does not. The lock pins a name, repository and version, with no content hash of the archive, so a locked fetch trusts the repository to serve the same content under that version forever. A vendored tarball is the artefact you tested. The cheaper equivalent is to package once and promote that `.tgz`, which embeds the same bytes without putting them in the source tree.
  • Your subchart is an operator chart that ships CRDs. Does vendoring change anything about it?
    Vendoring changes only how the archive reaches the render - the subchart's own behaviour is identical either way. What matters is that whichever copy you ship is the version you validated, since the resources a bundled operator brings along are exactly the kind you do not want changing under you between environments. That argues for a fixed artefact - vendored or packaged - rather than a fetch at deploy time.

Chart.lock is the receipt; vendoring charts/ is keeping the goods in your own warehouse. The receipt is always worth filing - the warehouse only pays for itself when you cannot trust the shop to still be open.

saying these in an interview costs you the question

  • Says committing charts/ removes the need for Chart.lock
  • Claims Helm verifies vendored archive versions against the lock
  • Thinks a packaged chart still downloads its subcharts at install
  • Treats vendoring as the only way to survive an upstream outage
  • Vendors tarballs while ignoring repository growth and blind reviews
  • Runs dependency update to fix drift in a deploy job

context