Real applications are rarely one component. They have a database, a cache, a message broker, a worker pool. Helm lets you compose charts hierarchically — though, as we will see, this power is easy to overuse.
Declaring Dependencies
Add them to Chart.yaml:
apiVersion: v2
name: my-app
version: 1.4.2
dependencies:
- name: postgresql
version: "13.x.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: postgresql.enabled
- name: redis
version: "18.x.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: redis.enabled
alias: cache # rename inside our chart
- name: common-lib
version: "2.0.0"
repository: "oci://ghcr.io/acme/charts"
import-values:
- child: globalDefaults
parent: defaults
Then resolve and download:
helm dependency update ./my-app
ls my-app/charts
# common-lib-2.0.0.tgz postgresql-13.4.1.tgz redis-18.7.0.tgz
helm dependency list ./my-app
helm dependency build ./my-app # uses Chart.lock; for reproducible builds in CI
The Chart.lock file records exact versions resolved. Commit it. Use helm dependency build in CI to install the locked versions (analogous to npm ci).
How Sub-Chart Values Work
The parent's values.yaml contains one key per sub-chart, named after the dependency (or its alias):
# Parent values.yaml
replicaCount: 3
postgresql:
enabled: true
auth:
database: myapp
username: myapp
primary:
persistence:
size: 20Gi
cache: # alias of 'redis'
enabled: true
architecture: standalone
auth:
enabled: false
Inside the sub-chart's templates, the same data appears under .Values directly — the parent's key is stripped. A sub-chart never knows it's a sub-chart.
Global Values
The reserved key global in parent values is shared with every sub-chart:
# Parent
global:
storageClass: gp3
imageRegistry: ghcr.io/acme
postgresql:
primary:
persistence: { size: 20Gi } # storageClass inherited from global
In the sub-chart, access via .Values.global.storageClass. Use sparingly — globals are easy to abuse and create implicit coupling.
Conditions and Tags
condition on a dependency points to a values key that turns the sub-chart on or off:
dependencies:
- name: postgresql
condition: postgresql.enabled
- name: redis
condition: cache.enabled
alias: cache
tags are a coarser switch — enable several dependencies with one toggle:
dependencies:
- name: postgresql
tags: [persistence]
- name: redis
tags: [persistence, cache]
# values.yaml
tags:
persistence: false # disables both
The condition wins over tags when both are set.
The Umbrella Chart Pattern
An "umbrella chart" is one with no templates of its own (or only a few utility templates) and many dependencies. Its purpose is to deploy a coherent stack — say, a full observability platform — with one helm install:
observability-stack/
├── Chart.yaml # dependencies: prometheus, grafana, loki, tempo, alertmanager
├── values.yaml # composed configuration for all five
└── templates/
└── _helpers.tpl
Trade-offs:
- Pros: single source of truth for the stack; one upgrade path; environment differences expressed in one values file.
- Cons: a change in any sub-chart blocks the whole upgrade; debugging crosses chart boundaries; values surface for the umbrella is huge.
Library Charts
A library chart (type: library in Chart.yaml) has no rendered resources — only named templates exposed for reuse. The chart is added as a dependency; its helpers become available via include:
{{ include "common-lib.deployment" . }}
{{ include "common-lib.labels" . }}
Useful for organisations standardising on consistent label sets, security contexts, or pod templates across many internal charts.
When NOT to Use Dependencies
Sub-charts couple lifecycles. A bad upgrade in PostgreSQL drags your app's chart with it. Common alternatives:
- Separate releases. Install PostgreSQL once; install the app chart with a values pointer at the existing database. Independent upgrade cadence, clearer ownership.
- Operators. For stateful services (Postgres, Kafka, Elasticsearch), a CNCF operator (CrunchyData, Strimzi, ECK) is almost always better than a stateful Helm chart.
- GitOps composition. An Argo CD
Applicationper chart; a parent App-of-Apps orchestrates. The composition lives in Git, not in YAML embedded in a chart.
The Right Mental Model
Use Helm dependencies for tightly-coupled sidecars and library helpers. Use separate releases or operators for anything stateful or independently versioned. Use GitOps to orchestrate the whole.