Chart Dependencies

Manage chart dependencies to compose complex applications. Use subcharts, conditions, and dependency overrides.

6 min read

Chart Dependencies

In the previous tutorial, we learned about named templates for code reuse within a chart. But what about reusing entire charts? If your app needs Redis, PostgreSQL, or NGINX, you don't have to write those charts yourself. You declare them as dependencies and Helm handles the rest.

Why Dependencies?

Real applications don't run alone. A typical web app might need:

  • A database (PostgreSQL)
  • A cache (Redis)
  • A message queue (RabbitMQ)
  • A reverse proxy (NGINX)

Without dependencies, you'd either:

  1. Cram everything into one massive chart (nightmare to maintain)
  2. Install each chart separately and wire them together manually (tedious)

Dependencies let you compose a single chart that brings along everything it needs.

Declaring Dependencies

Dependencies live in Chart.yaml:

# Chart.yaml
apiVersion: v2
name: my-web-app
version: 1.0.0
appVersion: "2.0.0"

dependencies:
  - name: postgresql
    version: "13.x.x"
    repository: https://charts.bitnami.com/bitnami
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami

Each dependency specifies:

FieldRequiredDescription
nameYesChart name in the repository
versionYesSemVer range to accept
repositoryYesChart repo URL or file:// path
conditionNoValues path that enables/disables
tagsNoGroup dependencies by tag
aliasNoRename the dependency
import-valuesNoImport child values into parent

Downloading Dependencies

After declaring dependencies, download them:

# Download dependencies into charts/ directory
helm dependency update ./my-web-app

# Or the shorter form
helm dep up ./my-web-app

This creates:

my-web-app/
├── Chart.yaml
├── Chart.lock          # Locked versions (like package-lock.json)
├── charts/
│   ├── postgresql-13.2.24.tgz
│   └── redis-18.4.0.tgz
├── values.yaml
└── templates/

The Chart.lock file pins exact versions so builds are reproducible. To rebuild from the lock file (without resolving new versions):

helm dependency build ./my-web-app

Version Ranges

Helm uses SemVer for version constraints:

dependencies:
  # Exact version
  - name: redis
    version: "18.4.0"

  # Patch range (18.4.x)
  - name: redis
    version: "~18.4.0"

  # Minor range (18.x.x)
  - name: redis
    version: "^18.0.0"

  # Explicit range
  - name: redis
    version: ">=18.0.0 <19.0.0"

  # Wildcard
  - name: redis
    version: "18.x.x"

"Which should I use?"

For most cases, "18.x.x" or "^18.0.0" is a good balance — you get bug fixes and minor features but no breaking changes.

Configuring Dependencies

Here's the cool part. You can override dependency values from your parent chart's values.yaml:

# values.yaml of the parent chart

# My app's values
replicaCount: 3
image:
  repository: my-web-app
  tag: "2.0.0"

# PostgreSQL dependency values (nested under the dependency name)
postgresql:
  auth:
    username: myapp
    password: secret123
    database: myapp_db
  primary:
    persistence:
      size: 10Gi

# Redis dependency values
redis:
  architecture: standalone
  auth:
    enabled: false
  master:
    persistence:
      size: 1Gi

The convention: nest dependency configuration under a key matching the dependency name. Helm passes those values to the sub-chart automatically.

Connecting to Dependencies

In your parent chart templates, you can reference the dependency services:

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  # PostgreSQL service is named <release>-postgresql by default
  DATABASE_URL: "postgresql://{{ .Values.postgresql.auth.username }}:{{ .Values.postgresql.auth.password }}@{{ .Release.Name }}-postgresql:5432/{{ .Values.postgresql.auth.database }}"
  # Redis service
  REDIS_URL: "redis://{{ .Release.Name }}-redis-master:6379"

Conditional Dependencies

Not every environment needs every dependency. Use condition to make dependencies optional:

# Chart.yaml
dependencies:
  - name: postgresql
    version: "13.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled
# values.yaml
postgresql:
  enabled: true
redis:
  enabled: false   # Skip Redis in dev

When redis.enabled is false, Helm skips the Redis sub-chart entirely — no Redis resources are created.

In your templates, adapt the configuration accordingly:

{{- if .Values.redis.enabled }}
  REDIS_URL: "redis://{{ .Release.Name }}-redis-master:6379"
{{- end }}

Tags — Grouping Dependencies

Tags let you enable/disable groups of dependencies at once:

# Chart.yaml
dependencies:
  - name: postgresql
    version: "13.x.x"
    repository: https://charts.bitnami.com/bitnami
    tags:
      - database
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami
    tags:
      - cache
  - name: prometheus
    version: "25.x.x"
    repository: https://prometheus-community.github.io/helm-charts
    tags:
      - monitoring
  - name: grafana
    version: "7.x.x"
    repository: https://grafana.github.io/helm-charts
    tags:
      - monitoring
# values.yaml
tags:
  database: true
  cache: true
  monitoring: false   # Disable both prometheus and grafana

Aliases — Multiple Instances

What if you need two Redis instances? Use alias:

# Chart.yaml
dependencies:
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami
    alias: redis-cache
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami
    alias: redis-session
# values.yaml
redis-cache:
  architecture: standalone
  auth:
    enabled: false

redis-session:
  architecture: standalone
  auth:
    enabled: true
    password: "session-secret"

Each alias creates a separate instance with independent configuration.

Local Dependencies

During development, you might want to reference a chart on your filesystem instead of a remote repository:

# Chart.yaml
dependencies:
  - name: my-common-lib
    version: "1.0.0"
    repository: "file://../my-common-lib"

The file:// protocol points to a local directory relative to the chart. This is perfect for:

  • Monorepos with multiple charts
  • Developing a library chart alongside an application chart
  • Testing changes before publishing

import-values — Flattening Configuration

By default, dependency values are nested under the dependency name. With import-values, you can import child values into the parent scope:

# Chart.yaml
dependencies:
  - name: postgresql
    version: "13.x.x"
    repository: https://charts.bitnami.com/bitnami
    import-values:
      - child: auth
        parent: database

Now instead of postgresql.auth.username, you can use database.username in your parent values.

Overriding Dependency Templates

Sometimes you need to customize how a dependency behaves. Your options:

  1. Override values — the preferred way (shown above)
  2. Post-render — use --post-renderer to patch output
  3. Fork the chart — last resort (you lose upstream updates)
# Post-render example with kustomize
helm install my-app ./my-chart --post-renderer ./kustomize-renderer.sh

Managing Dependencies Workflow

# 1. Add dependency to Chart.yaml

# 2. Download it
helm dep up ./my-chart

# 3. Check what's in charts/
ls ./my-chart/charts/

# 4. See the dependency tree
helm dep list ./my-chart

# 5. Install everything together
helm install my-release ./my-chart

# 6. When you update dependency versions:
helm dep update ./my-chart

What's Next?

Dependencies let you compose complex applications from reusable building blocks. Combined with conditions and aliases, you have fine-grained control over what gets deployed and how.

In the next tutorial, we'll learn about Helm hooks — special resources that run at specific points in a release lifecycle, like database migrations before an upgrade or cleanup jobs after a delete.