Managing multiple Helm charts within a single monorepo is a common pattern, but it can quickly become a tangled mess if not approached deliberately. The most surprising thing about this is that Helm itself doesn’t inherently understand monorepos; you’re essentially building a system around Helm to make it work.
Let’s see this in action. Imagine a monorepo structure like this:
.
├── apps/
│ ├── frontend/
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ └── templates/
│ │ └── ...
│ └── backend/
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
│ └── ...
├── libraries/
│ ├── common-utils/
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ └── templates/
│ │ └── ...
└── README.md
Here, apps/frontend and apps/backend are independent Helm charts, each deployable to a Kubernetes cluster. libraries/common-utils might be a shared component that other charts depend on.
The core problem this solves is dependency management and consistency across related applications. Instead of having separate Git repositories for each chart, you keep them together. This allows for atomic commits that can update multiple related charts simultaneously, ensuring they are always compatible. It also simplifies CI/CD pipelines: one pipeline can manage the build, test, and deployment of all charts in the monorepo.
Internally, Helm treats each directory containing a Chart.yaml as a distinct chart. The monorepo structure is just a filesystem arrangement. The magic happens when you invoke Helm commands. For example, to install the frontend chart, you’d navigate to its directory and run:
helm install my-frontend ./apps/frontend --namespace default
Or, if you’re in the root of the monorepo:
helm install my-frontend ./apps/frontend
The helm install command, when given a local path, simply looks for the Chart.yaml file in that directory and treats it as a chart.
When it comes to dependencies between charts within the monorepo, this is where things get interesting. Helm’s built-in dependencies feature typically points to external repositories (like ChartMuseum or Artifact Hub). To manage internal dependencies, you often resort to a build step.
Consider the apps/backend chart that depends on libraries/common-utils. You might have a Chart.yaml in apps/backend that looks like this:
# apps/backend/Chart.yaml
apiVersion: v2
name: backend
version: 0.1.0
description: A Helm chart for the backend service
dependencies:
- name: common-utils
version: "0.2.0" # This version *must* match the common-utils chart's version
repository: "file://../libraries/common-utils" # This is the key for local dependencies
When you run helm dependency update ./apps/backend, Helm will look at the repository: "file://../libraries/common-utils" directive. It will then copy the contents of ../libraries/common-utils into the charts/ subdirectory of apps/backend. This makes common-utils a local dependency that gets bundled with the backend chart during packaging or installation.
The exact levers you control are primarily directory structure, Chart.yaml configurations (especially dependencies with file:// repositories), and your CI/CD pipeline logic. You’ll need scripts to:
- Update Dependencies: Run
helm dependency updatefor each chart that has local dependencies. - Package Charts: Use
helm packageon individual charts or a tool that can iterate through your monorepo. - Lint/Test Charts: Run
helm linton each chart. - Deploy Charts: Use
helm installorhelm upgradepointing to the specific chart directories or packaged archives.
A common workflow involves a CI job that checks out the code, runs helm dependency update for all charts with local dependencies, then helm lint on all charts, and finally, based on what changed, deploys specific charts.
The one thing most people don’t know is that the file:// repository directive for local dependencies doesn’t link to the source chart; it copies the source chart’s contents into a charts/ subdirectory of the chart being processed. This means that if you update the common-utils chart, you must re-run helm dependency update on dependent charts like backend to incorporate those changes. If you forget this step, you’ll deploy an outdated version of the dependency.
The next concept you’ll likely grapple with is managing versions of these charts, especially when publishing them to an artifact repository for external consumption or for use in different environments.