Lesson 14 Dependencies & Subcharts
Theme:

Theory

Composing complex applications from reusable Helm chart building blocks

Why Dependencies Matter

Most real-world applications do not run in isolation. A typical web application needs a database, a cache layer, and possibly a message broker. Rather than bundling all of those Kubernetes manifests into a single monolithic chart, Helm lets you declare dependencies -- external charts that are pulled in automatically and rendered alongside your own templates.

This dependency model gives you several advantages: you can reuse battle-tested community charts (Bitnami PostgreSQL, Redis, etc.) without reinventing their templates, you can version-lock your infrastructure components independently, and you can toggle pieces on or off depending on the target environment.

Declaring Dependencies in Chart.yaml

Dependencies are declared in the dependencies block of your Chart.yaml. Each entry requires three fields: name, version, and repository.

apiVersion: v2
name: myapp
version: 1.0.0
appVersion: "2.5.0"

dependencies:
  - name: postgresql
    version: "13.2.24"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
    tags:
      - database

  - name: redis
    version: "18.6.1"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled
    tags:
      - cache

The name must match the chart name in the remote repository. The version field accepts exact versions or semantic version ranges. The repository is the URL of the Helm repository (or an OCI registry with the oci:// prefix).

helm dependency update vs. helm dependency build

After declaring dependencies you need to fetch them. Helm provides two commands for this:

Think of Chart.lock like package-lock.json in Node.js or poetry.lock in Python. Commit Chart.lock to version control and add charts/*.tgz to .gitignore.

The charts/ directory holds the downloaded archive files. You can also place unpacked chart directories here for local development -- Helm treats both formats identically.

Subchart Value Overrides

Every subchart has its own values.yaml with its own defaults. The parent chart can override any of those values by nesting them under a key that matches the dependency name:

# Parent values.yaml
postgresql:
  auth:
    username: myapp
    database: myapp_production
  primary:
    persistence:
      size: 50Gi
      storageClass: gp3

redis:
  architecture: standalone
  auth:
    enabled: false
  master:
    persistence:
      size: 5Gi

This layering means you never need to fork a subchart just to change a setting. The parent chart acts as the configuration authority, and the subchart templates consume whatever values they receive.

How Value Merging Works

Helm performs a deep merge. The subchart's default values.yaml forms the base, and the parent's overrides are merged on top. For maps, keys are merged recursively. For scalars and arrays, the parent's value replaces the child's entirely.

Global Values

Sometimes several subcharts need the same piece of configuration -- a shared image registry, a storage class, or a domain name. Rather than duplicating the value under every subchart key, Helm supports global values:

global:
  storageClass: "gp3"
  imageRegistry: "registry.example.com"
  imagePullSecrets:
    - name: registry-creds

Any chart -- parent or subchart -- can access these via .Values.global.storageClass, .Values.global.imageRegistry, etc. Global values are the recommended mechanism for cross-cutting configuration that must be consistent across the entire release.

Many Bitnami charts already honour global.storageClass and global.imageRegistry out of the box, so setting these globals automatically configures all your Bitnami dependencies.

Conditions and Tags

Not every dependency is needed in every environment. A local development setup might skip Redis, while production needs it. Helm provides two mechanisms to toggle dependencies:

Conditions

A condition is a dot-path into the parent's values that must resolve to a boolean. When set to false, the dependency is excluded entirely:

# Chart.yaml
dependencies:
  - name: redis
    condition: redis.enabled

# values.yaml
redis:
  enabled: true   # set to false to skip redis

Tags

Tags group multiple dependencies under a single toggle. Every dependency that carries a tag is enabled or disabled together:

# Chart.yaml
dependencies:
  - name: postgresql
    tags: [database]
  - name: mysql
    tags: [database]

# values.yaml
tags:
  database: true

When both a condition and a tag apply to the same dependency, the condition takes precedence. If the condition key exists in values, the tag is ignored for that dependency.

Library Charts

A library chart is a special chart type that contains only template helpers -- it never renders Kubernetes resources on its own. You declare it by setting type: library in its Chart.yaml:

apiVersion: v2
name: mylib
type: library
version: 1.0.0

Library charts are perfect for standardising labels, annotations, resource naming, and boilerplate across multiple application charts in your organisation. Any chart that lists a library as a dependency can call its named templates with {{ include "mylib.labels" . }}.

Because library charts have type: library, Helm skips them during manifest rendering -- their templates are available but produce no output unless explicitly included.

import-values

The import-values field in a dependency entry lets you pull specific values from a subchart's exports into the parent's value scope:

dependencies:
  - name: postgresql
    version: "13.2.24"
    repository: "https://charts.bitnami.com/bitnami"
    import-values:
      - child: primary.service
        parent: dbService
      - child: auth
        parent: dbAuth

After resolution, .Values.dbService in the parent chart contains whatever .Values.primary.service holds in the postgresql subchart. This is useful when the parent templates need to reference subchart-internal details -- for example, constructing a database connection string that uses the subchart's service name and port.

Dependency Version Ranges

Helm uses semantic versioning constraints to give you control over which versions are acceptable:

In production, prefer tilde ranges (~) or exact versions to avoid surprise breaking changes. Use caret ranges (^) during active development when you want the latest features.

OCI-Based Dependencies

Starting with Helm 3.8, charts can be stored in OCI-compliant container registries (Docker Hub, GitHub Container Registry, Amazon ECR, etc.). To reference an OCI dependency, use the oci:// scheme:

dependencies:
  - name: nginx
    version: "15.4.4"
    repository: "oci://registry-1.docker.io/bitnamicharts"

OCI dependencies do not require helm repo add. Helm pulls them directly from the registry using the OCI distribution spec. This simplifies CI/CD pipelines and aligns Helm chart storage with container image workflows.

Practical Example: Full Application Stack

Putting it all together, here is a realistic Chart.yaml for a web application that depends on PostgreSQL for persistence, Redis for caching, and a shared library chart for common templates:

apiVersion: v2
name: webapp
version: 2.0.0
appVersion: "3.1.0"

dependencies:
  - name: common-lib
    version: "1.x.x"
    repository: "oci://ghcr.io/myorg/charts"

  - name: postgresql
    version: "~13.2.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
    tags: [backend-storage]
    import-values:
      - child: primary.service
        parent: dbService

  - name: redis
    version: "~18.6.0"
    repository: "oci://registry-1.docker.io/bitnamicharts"
    condition: redis.enabled
    tags: [backend-storage]

With this structure, a developer can install the entire stack with helm install webapp ./webapp, disable Redis for local dev with --set redis.enabled=false, or switch storage classes globally with --set global.storageClass=standard.

Key Takeaways