<p>We have some services that can be installed in multiple locations with differing configurations. We've been asked to support multi-level configuration options using environment variables set with defaults, configmaps, secrets, and command-line options passed in via <code>helm install --set</code>. The following works, but is very cumbersome as the number of parameters for some of the services are numerous and the Values dot-notation goes a few levels deeper.</p>
<pre><code>env:
  # Set default values
  - name: MY_VAR
    value: default-value
  - name: OTHER_VAR
    value: default-other-value

  # Allow configmap to override
  - name: MY_VAR
    valueFrom:
      configMapKeyRef:
        name: env-configmap
        key: MY_VAR
        optional: true
  - name: OTHER_VAR
    valueFrom:
      configMapKeyRef:
        name: env-configmap
        key: OTHER_VAR
        optional: true

  # Allow secrets to override
  - name: MY_VAR
    valueFrom:
      secretsKeyRef:
        name: env-secrets
        key: MY_VAR
        optional: true
  - name: OTHER_VAR
    valueFrom:
      secretsKeyRef:
        name: env-secrets
        key: OTHER_VAR
        optional: true

  # Allow 'helm install --set' to override
  {{- if .Values.env }}
  {{- if .Values.env.my }}
  {{- if .Values.env.my.var }}
  - name: MY_VAR
    value: {{ .Values.env.my.var }}
  {{- end }}
  {{- end }}
  {{- if .Values.env.other }}
  {{- if .Values.env.other.var }}
  - name: OTHER_VAR
    value: {{ .Values.env.other.var }}
  {{- end }}
  {{- end }}
  {{- end }}
</code></pre>
<p>Using envFrom for the ConfigMap and Secrets would be nice, but tests and docs show this would not allow the command-line override, since <code>env:</code> and <code>envFrom:</code> doesn't mix in the way that's needed. As the <a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#container-v1-core" rel="nofollow noreferrer">v1.9</a> and <a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#container-v1-core" rel="nofollow noreferrer">v2.1</a> Kubernetes API states:</p>
<blockquote>
<p>envFrom: List of sources to populate environment variables in the
container. The keys defined within a source must be a C_IDENTIFIER.
All invalid keys will be reported as an event when the container is
starting. When a key exists in multiple sources, the value associated
with the last source will take precedence. Values defined by an Env
with a duplicate key will take precedence. Cannot be updated.</p>
</blockquote>
<p>Is there a better way to provide this default-&gt;configmap-&gt;secrets-&gt;cmd-line override precedence?</p>

Tog
<p>I found a solution that I mostly like. My issue was caused by giving too much weight to the &quot;Values defined by an Env with a duplicate key will take precedence&quot; comment in the docs, and thinking I needed to exclusively use Env. The defined precedence is exactly what I needed.</p>
<p>Here's the helm chart files for my current solution.</p>
<p><strong>configmap/templates/configmap.yaml</strong></p>
<pre class="lang-yml prettyprint-override"><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
data:
  {{- if .Values.env }}
    {{- toYaml $.Values.env | nindent 2 }}
  {{- end }}
  {{- if .Values.applicationYaml }}
  application.yml: |
    {{- toYaml $.Values.applicationYaml | nindent 4 }}
  {{- end }}
</code></pre>
<p><strong>secrets/templates/secrets.yaml</strong></p>
<pre class="lang-yml prettyprint-override"><code>apiVersion: v1
kind: Secret
metadata:
  name: my-secrets
type: Opaque
data:
  {{- range $key, $val := .Values.env }}
  {{ $key }}: {{ $val | b64enc }}
  {{- end }}
stringData:
  {{- if .Values.applicationYaml }}
  application.yml: |
    {{- toYaml $.Values.applicationYaml | nindent 4 }}
  {{- end }}
</code></pre>
<p><strong>deployment.yaml</strong></p>
<pre class="lang-yml prettyprint-override"><code>apiVersion: apps/v1
kind: Deployment
  ...
spec:
  ...
      containers:                                                                                     
      - name: my-deployment
        {{- if .Values.env }}
        env:
        {{- range $key, $val := .Values.env }}
        - name: {{ $key }}
          value: {{ $val }}
        {{- end }}
        {{- end }}
        envFrom:
        - configMapRef:
           name: my-configmap
        - secretRef:
           name: my-secrets
        volumeMounts:
        - name: configmap-application-config
          mountPath: /application/config/configmap/
        - name: secrets-application-config
          mountPath: /application/config/secrets/
      volumes:
        - name: configmap-application-config
          configMap:
             name: my-configmap
             optional: true
        - name: secrets-application-config
          secret:
             secretName: my-secrets
             optional: true
</code></pre>
<p>Since this is a Spring Boot app, I used volumeMounts to allow the application.yml default values to be overridden in the ConfigMap and Secrets. The order of precedence from lowest to highest is:</p>
<ul>
<li>the application's application.yml (v1 in following examples)</li>
<li>the configmap's applicationYaml (v2)</li>
<li>the secret's applicationYaml (v3)</li>
<li>the configmap env (v4)</li>
<li>the secret env (v5)</li>
<li>the helm install/uninstall --set (v6)</li>
</ul>
<p>To complete the example, here's test values yaml files and the command-line.</p>
<p><strong>app/src/main/resources/application.yml</strong></p>
<pre class="lang-yml prettyprint-override"><code>applicationYaml:
  test:
    v1: set-from-this-value
    v2: overridden
    v3: overridden
    v4: overridden
    v5: overridden
    v6: overridden
</code></pre>
<p><strong>configmap/values.yaml</strong></p>
<pre class="lang-yml prettyprint-override"><code>applicationYaml:
  test:
    v2: set-from-this-value
    v3: overridden
    v4: overridden
    v5: overridden
    v6: overridden

env:
   TEST_V4: set-from-this-value
   TEST_V5: overridden
   TEST_V6: overridden
</code></pre>
<p><strong>secrets/values.yaml</strong></p>
<pre class="lang-yml prettyprint-override"><code>applicationYaml:
  test:
    v3: set-from-this-value
    v4: overridden
    v5: overridden
    v6: overridden

env:
   TEST_V5: set-from-this-value
   TEST_V6: overridden
</code></pre>
<p><strong>command-line</strong></p>
<pre class="lang-bash prettyprint-override"><code>helm install --set env.TEST_V6=set-from-this-value ...
</code></pre>
<p>Ideally, I'd like to be able to use dot-notation instead of TEST_V6 in the env and --set fields, but I'm not finding a way in helm to operate only on the leaves of yaml. In other words, I'd like something like <code>range $key, $val</code>, but where the key is equal to &quot;test.v6&quot;. If that was possible, the key could be internally converted to an environment variable name with <code>{{ $key | upper | replace &quot;-&quot; &quot;_&quot; | replace &quot;.&quot; &quot;_&quot; }}</code>.</p>
