TT Lab
Get started
Learn Learning paths Courses

Authoring and Shipping Helm Charts

A Chart in a Repository Is Just a tgz and an index.yaml

Continue in TT Lab

Summary in one line

A chart repository is not server software but a static HTTP directory that serves one index.yaml and several tgz files.

Why this is needed

Even after building a chart well, everyone slips once at the stage of spreading it to the team. The most common way is to just tell people the Git repository path. helm install myapp ./charts/myapp runs fine. But this approach has no versions. The only way to check whether what you deployed yesterday and what you deploy today are the same chart is git log, and to roll back you have to memorize a commit hash. In the middle of a production incident, a moment comes when you cannot answer the question "exactly what state was the chart in at that time?"

A packaged chart answers that question with a single file. catalog-1.1.0.tgz has its name and version embedded in the file name, and if even one byte of the content differs, the digest in the index differs. Even if the deployment record says only "catalog 1.1.0," you can pull out again exactly what it was.

version and appVersion — two numbers that move separately

There are two numbers in Chart.yaml, and if you mistake them for the same thing, deployment conversations keep going off track.

Field What it is the version of When it goes up
version The chart itself When templates, defaults, or dependencies change
appVersion The software the chart carries When the application image tag changes

Even if you only fix the default resource limits, version must go up. The image to be deployed is the same, so appVersion stays. Conversely, if you only build the application anew and change the tag, appVersion goes up, and if you touched the templates to reflect that value, version goes up with it. So the two values are mostly different numbers, and their being the same is rather a coincidence.

Only version goes into the file name. Even if you overwrite appVersion with helm package --app-version, the tgz name stays the same. This is why helm search repo --versions and helm list show the two values side by side.

The fact that the index is not updated automatically

index.yaml is the repository's table of contents. But this table of contents does not grow by itself when you upload a tgz. A person has to run helm repo index <디렉터리> again (with the directory in the placeholder), and at that point this command looks at only the tgz files currently in that directory and rewrites the table of contents from scratch.

helm package catalog --version 1.2.0 -d stage
helm repo index stage --merge repo/index.yaml   # 옛 목차를 합친다

If you build an index from stage, which contains only build outputs, without --merge and upload it, the table of contents keeps only the one version you just built. The tgz files are still on the server, yet they disappear from helm search and helm pull — the files were not deleted but dropped from the table of contents, so looking at the disk does not reveal the cause.

The receiving side also holds the table of contents as a cache. When you do helm repo add, a copy comes down as ~/.cache/helm/repository/<이름>-index.yaml (with the repository name in the placeholder), and helm search reads this copy, not the server. If a new version was uploaded to the repository but does not show up in search, it is mostly because helm repo update was not run.

Other routes besides an HTTP repository

index.yaml + tgz is not the only way to carry charts. Helm 3 can use an OCI registry as a chart repository, and you upload with helm push <차트.tgz> oci://<레지스트리>/<경로> (chart file, registry, and path in the placeholders). This approach has no table of contents — because the registry already has the list of tags. So the accident of wiping out old versions by forgetting helm repo index --merge cannot structurally happen, and it uses the same authentication and permission system as container images. Conversely, it is hard to skim everything at once with helm search repo, and the level of support differs by registry type.

There is also a device on the integrity side. If you package with helm package --sign --key <이름> --keyring <경로> (key name and keyring path in the placeholders), a .prov file is created next to the tgz, and the receiving side checks the signature with helm verify or helm install --verify. The index digest confirms that the file was not changed in transit but does not tell you who made it, which is how the roles of the two differ. For a repository used only inside the company, the digest is usually enough, and for charts distributed externally, you lean toward attaching a signature.

What it looks like in the field

When people first set up an in-house repository, they look for a dedicated server. In reality, it ends with opening one object storage bucket as a static website and having CI run helm package and helm repo index --merge and upload the results. If authentication is needed, you put a reverse proxy in front. This simplicity is both the strength and the trap — because the responsibility for updating the table of contents lies entirely with the pipeline, a single wrongly written index wipes the history of the whole repository.

On the dependencies side, problems from not distinguishing helm dependency build from update are frequent. update re-resolves the version range in Chart.yaml and rewrites Chart.lock. If you left the range open like ^1.0.0, yesterday's and today's builds may pull in different charts. build fetches the versions written in the lock as they are, and if the lock is out of sync with Chart.yaml, it stops without fetching. If you are using update in CI, that pipeline is not doing reproducible builds.

What you will do in the next lab

You package a chart in two versions, build an index, and serve that directory with python3 -m http.server to register it as a real repository. You pick a version and pull it, upload a third version merged in with --merge, and then lock it as a dependency of another chart. At the end you deliberately create a state where the declaration and the lock are out of sync and see for yourself what words helm dependency build stops with.