skip to content

Publishing a Chart

helm repo index over a directory of .tgz files, --merge to extend an existing index, static hosting, or pushing the same package to a registry instead. Probed on why a version is never re-published.

part ofHelmoverview, primer and where to startread it →
on this pageshow

questions

4

How do you publish a packaged Helm chart so others can install it with helm repo add?

level: juniorimportance: must knowfreq 66%

answer

  1. A repository is only static files
  2. One generated file lists the catalogue
  3. Generate it from the directory of tarballs
  4. helm repo index with a base URL
  5. entries, urls, digest, created

basics

~20 s

Collect the packaged .tgz files in one directory, run helm repo index on that directory to generate index.yaml, and serve the directory over HTTP. A chart repository is static files - no chart-server software is required.

solid answer

~40 s

Publishing is three steps: package, index, serve. `helm package` produces a versioned `.tgz`; you drop it into a directory alongside the other published tarballs and run `helm repo index <dir> --url <public-base-url>`, which writes `index.yaml` into that directory. That file *is* the repository protocol: `apiVersion: v1`, an `entries` map keyed by chart name, one record per published version carrying the chart's metadata, a `created` timestamp, a sha256 `digest` of the tarball and the `urls` it can be fetched from. Serving is whatever can serve static files - an object-store bucket, a static-site host, an internal web server. Consumers run `helm repo add` against the base URL, which fetches `index.yaml` and caches it, so a newly uploaded version becomes visible to them only after they refresh that cache with `helm repo update`.

code

bash · 9 lines
bash
helm package ./digest-builder            # -> digest-builder-2.7.3.tgz
mkdir -p site/charts
mv digest-builder-2.7.3.tgz site/charts/
helm repo index site/charts --url https://charts.internal.example/charts

# upload site/charts/ (tarballs + index.yaml) to any static host, then:
helm repo add internal https://charts.internal.example/charts
helm search repo internal/digest-builder
helm install digest internal/digest-builder --version 2.7.3

go deeper

for a junior

Be able to name the three steps in order - package, index, serve - and say that the repository is just files behind a URL. Knowing that consumers must run helm repo update before they see a new version is the detail interviewers listen for.

for a middle

Explain what index.yaml actually contains and why the client needs it: entries keyed by chart name, one record per version with metadata, digest and urls. Be ready to say what --url changes and why regenerating the index is a mandatory step, not a nicety.

for a senior

Show that you have run this as a real publish path: where the files are hosted, how the upload and re-index are ordered so consumers never see an index referencing a missing tarball, and how caching on the client and any CDN in front affects when a release becomes visible.

for a principal

Own the question of whether this channel is right at all. A single catalogue file that every client downloads on update, no upload authorisation beyond the web host, and no built-in enforcement of one-shot versions are architectural facts to plan around, not details.

A Helm chart repository is a place where packaged charts are stored and shared, and the useful thing to internalise is how little it is: a directory of `.tgz` files plus one generated file named `index.yaml`, reachable over HTTP or HTTPS. There is no repository daemon, no upload API, and no database. Any static file host works - an object-store bucket behind a CDN, a static-site host, a plain web server, or a directory exported by a build machine. ## The three steps **Package.** `helm package ./digest-builder` reads the chart directory and writes `digest-builder-2.7.3.tgz`, named from the `name` and `version` in `Chart.yaml`. The version in the filename is the chart's own version, not the version of the application it deploys. **Index.** `helm repo index <dir>` walks that directory, opens every chart package it finds, and writes `index.yaml` into the same directory. Crucially it indexes the *packages present in the directory at that moment* - it does not remember what was there before and it does not read chart source directories. Add `--url https://charts.internal.example/charts` so each entry records where the package is actually served from. **Serve.** Upload the directory - tarballs and `index.yaml` together - to anything that returns those files over HTTP. That is the entire server side. ## What is inside index.yaml ```yaml apiVersion: v1 entries: digest-builder: - name: digest-builder version: 2.7.3 appVersion: "1.9.4" apiVersion: v2 description: Builds and sends the daily email digest created: "2026-08-19T09:14:22.481337Z" digest: 9b4c1d7a2f6e08c35ad91be47f02c6d8a5e3b19047fc2d6081ea35b7c94d20f6 urls: - https://charts.internal.example/charts/digest-builder-2.7.3.tgz generated: "2026-08-19T09:14:22.481337Z" ``` Note the two different `apiVersion` fields, which trip people up constantly: the top-level `apiVersion: v1` is the *index file format*, while the `apiVersion: v2` inside the entry is the chart format copied out of that chart's `Chart.yaml`. The rest of the entry is that chart's metadata - description, keywords, maintainers, annotations, declared dependencies - lifted from `Chart.yaml` so a client can display and filter charts without downloading any of them. `digest` is the sha256 of the tarball, `created` is when the entry was generated, and `urls` is the list the client will actually fetch from. `entries` is a map from chart *name* to a list of published versions, so one repository can carry many charts and many versions of each. Helm sorts each list so that installing without a version constraint resolves to the newest published version. ## What the consumer does with it `helm repo add internal https://charts.internal.example/charts` fetches `<base>/index.yaml` once and stores it locally under a name. From then on `helm search repo`, `helm show`, `helm pull` and `helm install internal/digest-builder --version 2.7.3` all resolve against that cached copy, then download the tarball from the entry's `urls`. That indirection is why the tarballs do not have to live next to the index: `--url` can point at a different host or path entirely. If entries carry a bare filename instead of an absolute URL, the client resolves it relative to the repository URL it was added with. The cache is also why publishing is not instantaneous from the consumer's point of view. Until they run `helm repo update`, their Helm behaves as though your new version does not exist - not a bug, just the protocol. ## Publishing the next version There is no upload command for this kind of repository. `helm push` exists but targets an OCI registry, not an `index.yaml` repository. Publishing 2.7.4 means: bump the chart version, `helm package`, put the new tarball where the old ones live, regenerate `index.yaml` so it lists the new package alongside the existing ones, and upload both. The regeneration step is the one people forget - an uploaded tarball that no index entry mentions is invisible, because clients never list your directory, they only read `index.yaml`. ## What this shape gives you, and what it costs The upside is that the hosting problem disappears: static files are cheap, cacheable, trivially mirrored and easy to make highly available. The cost is that everything else is your job. There is no authentication beyond whatever the web host offers, no upload atomicity, no enforcement that a version is published once, and one catalogue file that every client downloads whole on every update - a file that grows with every version you ever publish. Those are the constraints that make the operational questions about publishing interesting.

  • Where does helm repo index write its output, and what happens to an index.yaml already sitting in that directory?
    It writes `index.yaml` into the directory being indexed, replacing whatever `index.yaml` is there. The generated file describes exactly the packages found in that directory at that moment, so any version that was listed before but is no longer present as a `.tgz` disappears from the catalogue unless you explicitly fold the previous index back in.
  • A consumer already ran helm repo add against your repository. What must they do to see the version you just uploaded?
    Run `helm repo update`. The client keeps a cached copy of `index.yaml` from when the repository was added or last updated, and resolves chart names and versions out of that cache. Until it refetches your index, `helm search repo` and `helm install` behave as if the new version does not exist. Nothing pushes the change to them.
  • Do the chart tarballs have to be served from the same host as index.yaml?
    No. The client fetches whatever each entry's `urls` field says, which is exactly what `--url` sets when you generate the index, so packages can live on a different host or path - a bucket or CDN edge, for instance - while the index is served elsewhere. When index and packages sit under the same base URL, a bare filename also works because the client resolves it relative to the repository URL.

It is closer to a printed catalogue left on a shelf than to a shop: you restock the shelf, then reprint the catalogue, and customers only learn about the new item when they pick up a fresh copy.

saying these in an interview costs you the question

  • Thinks a chart repository needs dedicated server software
  • Believes helm push uploads to an index.yaml repository
  • Uploads the tarball but never regenerates the index
  • Thinks index.yaml is written and maintained by hand
  • Expects consumers to see a new version without refreshing
  • Confuses the index apiVersion v1 with the chart apiVersion v2

context

open as a page

Why must an already-published Helm chart version never be overwritten with new content?

level: seniorimportance: should knowfreq 47%

basics

~20 s

Nothing in Helm enforces it, but every consumer treats chart name plus version as an identity: cached indexes, pinned dependencies and stored release records all resolve by version. Overwriting makes one coordinate mean two different things. Bump the version instead.

open as a page

How do you choose between publishing your Helm charts to a static index.yaml repository and to a registry?

level: principalimportance: should knowfreq 40%

basics

~20 s

Both channels ship the identical .tgz, so decide on operations, not features: a static index is one file on any web host carrying the whole catalogue; a registry reuses the credentials, replication and retention you already run.

open as a page

What does helm repo index --merge do that regenerating the index alone does not?

level: middleimportance: nice to knowfreq 34%

basics

~20 s

helm repo index builds index.yaml from only the packages present in the directory. --merge folds an existing index.yaml into that result, so versions whose tarballs are not in that directory keep their entries instead of disappearing.

open as a page