How to Migrate from Helm Monorepo to Versioned Charts

Aleksei Krugliak
4 min readJun 12, 2024

--

Image generated using Raycast AI

Background

In Altenar, there was a Helm monorepo for many years, where all component charts and the library chart were stored.

It looked something like this:

.
├── project_1
├── ...
├── project_x
└── library
├── Chart.yaml
└── templates

This setup works very simply — you keep only the current charts in the master branch, they are not versioned, and all have one chart version 0.1.0

All charts have the library chart as a dependency, using the local path:

# component/Chart.yaml
dependencies:
- name: library
version: 0.1.1
repository: file://../library

And in the templates directory, they simply include the necessary templates from the library:

# component/templates/deployment.yaml
{{- include "templates.deployment" . -}}

The library itself contains many different templates, such as deployments, services, and other things like cronjobs.

We initially took this example from here: https://faun.pub/dry-helm-charts-for-micro-services-db3a1d6ecb80 (probably because there were fewer examples in the Helm documentation back then).

In the CI pipeline, the repository is downloaded on the agent and packaged into an artifact, which is versioned and passed to the CD pipeline.

Then, in the CD part, several bash commands are executed, something like this:

$ cd component
$ helm dependency update .
$ helm upgrade --install component -f values.yaml .

And the application is deployed as if you did it manually from the repository.

This worked successfully for many years, but there is always room for improvement.

Some time ago, we worked with a Flux monorepo (you can read about it in a previous medium article), and in one of the new projects, the idea arose to use Flux objects HelmRelease for deploying our components.

In fact, this is not just a whim, but we plan to develop an operator for managing logically connected components, where one of the options is precisely the use of HelmRelease objects, but that will be in another article.

It’s time to start finally collecting charts with versioning and storing them in our registry, as these objects need properly versioned charts.

Why versioned charts are better

Dependencies: Versioning allows you to specify exactly which version of the library is needed for a particular chart to work. And there can be more than one.

  • Stability: Updates or changes in the library will not affect current configurations. If the chart successfully lives on an old version of the library, you don’t need to do anything extra, semver successfully signals incompatible changes.
  • Reuse: You can reuse old versions of charts, which allows you to guarantee the same state in different environments.
  • Rollback: In case of problems, you can easily roll back to the previous version of the chart by simply changing the version. In the current scheme, you need to roll back changes in the Helm repo and start building charts and deploying components again.
  • Changelog: And if you have taught each other to read version changes, you know what has changed in the chart/library.

In general, using versioning improves the manageability, reliability, and predictability of chart deployments.

And one more thing. For HelmRelease objects, we need already built and accessible charts from the registry. More details here: https://fluxcd.io/flux/components/source/helmcharts/#version

And if you don’t specify a version and always pull the latest, you know how it ends.

Anyway, even Kyverno has a best practice rule about that (for Docker images, but it’s a similar situation).

https://kyverno.io/policies/best-practices/disallow-latest-tag/disallow-latest-tag

To test an unknown approach, I played with a demo and decided to document it in two repositories that demonstrate and help repeat my path.

Preparing the library chart

In my case, I have an almost ready library, but I needed to understand how to build it, version it, and upload it to some registry.

For experiments with the OCI registry, I chose https://quay.io and GitHub for storing the code.

I took the official examples as the basis of the code.

All commands are detailed in the demo-helm-library repository.

In a nutshell — you need to create your registry, log in to it with Helm

helm registry login -u $QUAY_USERNAME -p $QUAY_PASSWD_ENCRYPTED quay.io

Then package the chart and push it to the registry

helm package .
helm push demo-library-0.1.1.tgz oci://quay.io/$QUAY_USERNAME/test-helm

After this, you will have a library chart uploaded to your registry.

Using the library chart in other charts

Here it’s even simpler, you need to add the library as a dependency in your chart

dependencies:
- name: demo-library
version: 0.1.1
repository: oci://quay.io/greengrunt/test-helm

Then build these dependencies, package, and push the chart to the registry

helm dependency update .
helm package .
helm push demo-chart-0.1.0.tgz oci://quay.io/$QUAY_USERNAME/test-helm

Everything is also detailed in another repository demo-helm-chart

--

--