Helm Values Inheritance One-Liner



A Helm template one-liner that adds an entire new feature. Sub charts can now inherit default values from their parent chart. This allow for per environment (develop/stage/production) default values, only overridden in the occasional service.

The One-Liner

It goes like this:

{{- $merged := merge (deepCopy .Values) (deepCopy (default (dict) .Values.global)) -}}

It can be easier understood by looking at the naive implementation:

{{- $merged := merge .Values .Values.global -}}

We create the template variable $merged, that is just like .Values, but where .Values.global provide the defaults.

The purpose of deepCopy and default is to dodge null pointer exceptions and avoid modifying the .Values them selves.

The deepCopy template function was added in Helm 2.16, so make sure you have upgraded to at least that (or ideally Helm 3).

Understanding .Values.global

To understand this one-liner it's important to understand .Values.global. This global value holds special meaning to Helm. It can be accessed from any chart or subchart.

So where it's usually the case that parent charts override values in sub charts, the .Values.global goes in the other direction, and allows a subchart to read values from the parent.

Read more in the Helm docs: https://helm.sh/docs/chart_template_guide/subcharts_and_globals/#global-chart-values

Usage Example with Environment Parent Chart

Say you have one helm chart per environment. For example your company has develop, stage, and production environments and you are using one parent helm chart for each.

Each such parent environment Helm chart has numerous dependency sub charts. Each sub chart might be a microservice.

It's probably the case that the values.yaml in these sub charts contains some repetition. Your PostgreSQL database connection information for example, or the URL for Elastic APM. Such values that are similar in most sub charts are also bound to be configured the same way in the whole environment.

For example the PostgreSQL hostname might be the very same for all micro services in develop. They are also the same in production, just another hostname there.

It's then quite convenient to just specify this hostname once in the .global section of the parent chart.

Parent Chart values.yaml:

global:
  postgres:
    hostname: the-development-hostname.com
    port: 5432
...

Sub Chart templates/service.yaml:

{{- $merged := merge (deepCopy .Values) (deepCopy (default (dict) .Values.global)) -}}
apiVersion: apps/v1
kind: Deployment
...
      env:
        - name: DB_HOST
          value: {{$merged.postgres.host}}
        - name: DB_PORT
          value: {{$merged.postgres.port}}

So where you would normally have written {{.Values.postgres.host}} you now just replace .Values with $merged for the default value inheritance to kick in like {{$merged.postgres.host}}.

Parent Chart Impact

Leveraging this technique you can simplify a parent chart values.yaml that looks like this:

service1:
  image:
    tag: 1.2.3
  postgres:
    hostname: the-development-hostname.com
    port: 5432

service2:
  image:
    tag: 5.0.0
  postgres:
    hostname: the-development-hostname.com
    port: 5432

service3:
  image:
    tag: 3.3.0
  postgres:
    hostname: the-development-hostname.com
    port: 5432

To just this:

global:
  postgres:
    hostname: the-development-hostname.com
    port: 5432

service1:
  image:
    tag: 1.2.3

service2:
  image:
    tag: 5.0.0

service3:
  image:
    tag: 3.3.0

Placement

So I usually place this line at the very top of every templates/service.yaml sub chart file:

{{- $merged := merge (deepCopy .Values) (deepCopy (default (dict) .Values.global)) -}}

That way the $merged variable is available in the whole rest of the file.

Automatic Version

The one-liner described so far is opt in. The .Values must be explicitly replaced with $merged.

By removing the variable assignment and deepCopy on the left side we achieve a different effect:

{{- merge .Values (deepCopy (default (dict) .Values.global)) -}}

The .Values are now mutated in place instead and the inheritance takes effect everywhere automatically.