Right, so you’ve built a chart. Congratulations, it’s a beautiful, self-contained snowflake. Now, let’s get real. Almost nothing useful in the Kubernetes ecosystem lives in total isolation. Your app probably needs a database, a redis cache, or maybe it depends on some other internal service. You could just tell the user to install those first, but we’re not barbarians. We’re engineers. We automate things. This is where Helm chart dependencies come in, and you’ve got two main ways to handle them: the old way (requirements.yaml) and the more modern, integrated way (sub-charts).

Let’s start with the modern approach because it’s conceptually simpler, even if it has its own quirks.

The charts/ Directory: Your Built-in Sub-charts

Think of the charts/ directory in your chart root as a magic box. Any chart you drop in there becomes a sub-chart of your main chart. Helm will install it automatically as part of the release. No questions asked.

my-awesome-chart/
├── charts/
│   └── redis-ha/ # <-- Just a chart you downloaded and dropped in
│       ├── Chart.yaml
│       ├── templates/
│       └── ... 
├── Chart.yaml
├── templates/
└── values.yaml

The beautiful part? It just works. The infuriating part? You have absolutely no control over it from your parent chart’s Chart.yaml. That chart is included, in its entirety, every single time. You need to manage its version by manually replacing the chart in the directory. It’s a great trick for rapid, local development or when you’re forking and customizing a specific version of a chart and want to treat it as a permanent fixture. For any semblance of version control or flexibility, you’ll want to use…

requirements.yaml: The Dependency Manager

The requirements.yaml file is the proper, declarative way to manage dependencies. It’s a separate file where you list what you need, where to get it, and what version you want. Helm will then go fetch them for you and place them in the charts/ directory.

Here’s what a simple one looks like. You gotta love a system that uses a file named requirements.yaml to define dependencies. The Pythonistas at Google must have been nodding approvingly.

# requirements.yaml
dependencies:
  - name: redis
    version: "18.0.0" # Yeah, the chart version, not the app version. Pay attention.
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled # This is the key to not installing things you don't want.
  - name: postgresql
    version: "13.0.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
    tags:
      - database
      - storage

The repository can be a URL (like above) or the name of a repo you’ve added locally with helm repo add. You can also reference a chart using a relative path (file://../my-other-chart) which is fantastic for local development.

Once you’ve defined this, run helm dependency update. This command is the workhorse. It reads your requirements.yaml, goes out to the specified repositories, downloads the tarballs of the charts you specified, and places them in your charts/ directory. It also creates a Chart.lock file, which is your reproducible build artifact, pinning the exact versions that were fetched.

The Art of Conditioning and Tagging

You don’t always want to install every dependency. Maybe the user is bringing their own Redis. This is where condition and tags save the day.

A condition is the primary switch. It’s a value path (usually something like redis.enabled) that you, the chart author, define in your parent chart’s values.yaml. If that value evaluates to true, the chart is installed. If not, it’s skipped. It’s that simple.

# in your parent values.yaml
redis:
  enabled: true

Tags are more flexible. You can assign multiple tags to a dependency and then enable/disable groups of them with a single value. In the requirements.yaml example above, the postgresql chart has the tags database and storage. You could then have a value in your parent chart that disables all “storage” related dependencies at once.

# in your parent values.yaml
tags:
  storage: false # This would disable the postgresql chart because it has the 'storage' tag.

Pro Tip: The condition always overrides tags. If condition: postgresql.enabled is false, the chart won’t install, even if its tags are enabled. This is your escape hatch for granular control.

The Values Cascade: Taming the Sub-chart Beast

This is the part that causes the most confusion. You have a values.yaml file for your main chart. The sub-chart also has its own values.yaml. How do you override the sub-chart’s values from the parent?

You do it by creating a section in your parent’s values.yaml that matches the name of the dependency (the one from requirements.yaml), not the name of the chart it contains. This is a critical distinction.

Let’s say your dependency is defined as name: redis in your requirements.yaml. The actual Bitnami chart is named redis-ha. To override its values, you don’t use redis-ha, you use redis.

# in your parent values.yaml
redis: # <- This matches the 'name' in requirements.yaml, not the chart name.
  enabled: true
  architecture: standalone
  auth:
    password: "my-super-secure-password" # Overriding the sub-chart's default value.

# These values will be merged into the sub-chart's own values,
# with your parent's values taking precedence.

This design is both brilliant and a bit mad. It means the parent chart’s values structure is dictated by the arbitrary name you gave the dependency, creating a loose coupling that can be confusing to debug. My advice: always check the sub-chart’s values.yaml to know what keys you can actually override. It’s the only way to be sure.

So, which should you use? Use requirements.yaml for almost everything. It’s declarative, version-controlled, and flexible. Reserve the charts/ directory for those truly vendored, customized charts that are more part of your application than a separate dependency. And always, always run helm dependency update after changing your requirements.yaml. Forgetting that is the number one cause of “but I fixed it!” followed by utter confusion.