132

I have my deployment.yaml file within the templates directory of Helm charts with several environment variables for the container I will be running using Helm.

Now I want to be able to pull the environment variables locally from whatever machine the helm is ran so I can hide the secrets that way.

How do I pass this in and have helm grab the environment variables locally when I use Helm to run the application?

Here is some part of my deployment.yaml file

...
...
    spec:
      restartPolicy: Always
      containers:
        - name: sample-app
          image: "sample-app:latest"
          imagePullPolicy: Always
          env:          
            - name: "USERNAME"
              value: "app-username"
            - name: "PASSWORD"
              value: "28sin47dsk9ik"
...
...

How can I pull the value of USERNAME and PASSWORD from local environment variables when I run helm?

Is this possible? If yes, then how do I do this?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
uberrebu
  • 3,597
  • 9
  • 38
  • 73

8 Answers8

158

You can export the variable and use it while running helm install.

Before that, you have to modify your chart so that the value can be set while installation.

Skip this part, if you already know, how to setup template fields.


As you don't want to expose the data, so it's better to have it saved as secret in kubernetes.

First of all, add this two lines in your Values file, so that these two values can be set from outside.

username: root
password: password

Now, add a secret.yaml file inside your template folder. and, copy this code snippet into that file.

apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-auth
data:
  password: {{ .Values.password | b64enc }}
  username: {{ .Values.username | b64enc }}

Now tweak your deployment yaml template and make changes in env section, like this

...
...
    spec:
      restartPolicy: Always
      containers:
        - name: sample-app
          image: "sample-app:latest"
          imagePullPolicy: Always
          env:          
          - name: "USERNAME"
            valueFrom:
              secretKeyRef:
                key:  username
                name: {{ .Release.Name }}-auth
          - name: "PASSWORD"
            valueFrom:
              secretKeyRef:
                key:  password
                name: {{ .Release.Name }}-auth
...
...

If you have modified your template correctly for --set flag, you can set this using environment variable.

$ export USERNAME=root-user

Now use this variable while running helm install,

$ helm install --set username=$USERNAME ./mychart

If you run this helm install in dry-run mode, you can verify the changes,

$ helm install --dry-run --set username=$USERNAME --debug ./mychart
[debug] Created tunnel using local port: '44937'

[debug] SERVER: "127.0.0.1:44937"

[debug] Original chart version: ""
[debug] CHART PATH: /home/maruf/go/src/github.com/the-redback/kubernetes-yaml-drafts/helm-charts/mychart

NAME:   irreverant-meerkat
REVISION: 1
RELEASED: Fri Apr 20 03:29:11 2018
CHART: mychart-0.1.0
USER-SUPPLIED VALUES:
username: root-user

COMPUTED VALUES:
password: password
username: root-user

HOOKS:
MANIFEST:

---
# Source: mychart/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: irreverant-meerkat-auth
data:
  password: password
  username: root-user
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: irreverant-meerkat
  labels:
    app: irreverant-meerkat
spec:
  replicas: 1
  template:
    metadata:
      name: irreverant-meerkat
      labels:
        app: irreverant-meerkat
    spec:
      containers:
      - name: irreverant-meerkat
        image: alpine
        env:
        - name: "USERNAME"
          valueFrom:
            secretKeyRef:
              key:  username
              name: irreverant-meerkat-auth
        - name: "PASSWORD"
          valueFrom:
            secretKeyRef:
              key:  password
              name: irreverant-meerkat-auth

        imagePullPolicy: IfNotPresent
      restartPolicy: Always
  selector:
    matchLabels:
      app: irreverant-meerkat

You can see that the data of username in secret has changed to root-user.

I have added this example into github repo.

There is also some discussion in kubernetes/helm repo regarding this. You can see this issue to know about all other ways to use environment variables.

jhanschoo
  • 1,236
  • 13
  • 15
  • 16
    works like a charm..thanks a lot man...really appreciate the time you put in to answer the question – uberrebu Apr 20 '18 at 03:05
  • 3
    one quick question...what if i have like 50 environment variable i want to pull...is there a way to simplify things when the variables are many? otherwise the helm install command will be pretty lengthy with --set for like 50 variables – uberrebu Apr 20 '18 at 03:11
  • 2
    @uberrebu you would have to use the range function to pass multiple environment variables at once during installation. Those variables can be set via a file with all the values and passed via -f flag. If you ask a new question I can provide a more comprehensive answer. – iomv May 31 '18 at 09:40
  • 2
    Question, how would you do that if there's only configuration data and nothing sensitive, where you should put data different as secrets? – Javier Salas Oct 09 '18 at 14:32
  • 3
    Passing the values on the command line is less than ideal. I wish Helm would pull in the env var values directly in a manner that Terraform uses. – Alan Cabrera Jan 26 '19 at 23:05
  • @alan any improvement on helm, regarding this? – Sobiaholic Aug 19 '19 at 12:16
  • Shouldn't the values in the example be `b64enc`'d as in https://helm.sh/docs/chart_template_guide/#configmap-and-secrets-utility-functions ? – jhanschoo Sep 22 '19 at 12:08
  • is there any way to use .env or .env.local files?, I think for now the only way for that is doing a Makefile (I made the whole circle here) – Joe Cabezas Mar 15 '20 at 06:27
48

you can pass env key value from the value yaml by setting the deployment yaml as below :

spec:
  restartPolicy: Always
  containers:
    - name: sample-app
      image: "sample-app:latest"
      imagePullPolicy: Always
      env:          
        {{- range $name, $value := .Values.env }}
        - name: {{ $name }}
          value: {{ $value }}
        {{- end }}

in the values.yaml :

env:          
 - name: "USERNAME"
   value: ""
 - name: "PASSWORD"
   value: ""

when you install the chart you can pass the username password value

helm install chart_name --name release_name --set env.USERNAME="app-username" --set env.PASSWORD="28sin47dsk9ik"
Oliver
  • 27,510
  • 9
  • 72
  • 103
Ramzi Hosisey
  • 715
  • 6
  • 5
  • 2
    Wouldn't do it this way bc everyone can see the password in your k8s dashboard if you upload the value like that. That's why the resource Secret was invented :) – Lea Reimann Jul 09 '21 at 04:58
  • 2
    The OP was not asking about best practices, just how to do it, so this answer is valid and a nice way to do it. One gotcha is that lists do not get merged, they get replaced. So when done this way, the values file for each release (eg in different clusters) must include the entire set of env vars. Often you will have one set that is more static, that you put right in the chart and reference individual values in values file, and another set of "extra" ie optional env vars that are from such a loop. – Oliver Dec 02 '21 at 20:41
  • Is there a way to directly use a particular environment variable in the deployment yaml file? Something like `{{ .Values.env.USERNAME }} or something? – Ram Patra Aug 09 '22 at 16:17
29

For those looking to use data structures instead lists for their env variable files, this has worked for me:

spec:
  containers:
    - name: {{ .Chart.Name }}
      image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
      imagePullPolicy: {{ .Values.image.pullPolicy }}
      env:          
      {{- range $key, $val := .Values.env }}
        - name: {{ $key }}
          value: {{ $val | quote }}
      {{- end }}

values.yaml:

env:          
 FOO: "BAR"
 USERNAME: "CHANGEME"
 PASWORD: "CHANGEME"

That way I can access specific values by name in other parts of the helm chart and pass the sensitive values via helm command line.

diavol
  • 671
  • 6
  • 8
15

To get away from having to set each secret manually, you can use:

export MY_SECRET=123
envsubst < values.yaml | helm install my-release . --values -

where ${MY_SECRET} is referenced in your values.yaml file like:

mychart:
  secrets:
    secret_1: ${MY_SECRET}

But be warned, this is not very secure! Your secrets will be stored plaintext in the values yaml.

zimvak
  • 171
  • 2
  • 7
  • This is not working for me because I don't have this command "envsubst" available in my CI pipeline. – jpmottin Feb 24 '22 at 07:17
  • Your system might not have the binary. Try installing it: https://www.gnu.org/software/gettext/manual/html_node/envsubst-Invocation.html – zimvak Nov 15 '22 at 01:08
12

Helm 3.1 supports post rendering (https://helm.sh/docs/topics/advanced/#post-rendering) which passes the manifest to a script before it is actually send to Kubernetes API. Post rendering allows to manipulate the manifest in multiple ways (e.g. use kustomize on top of Helm).

The simplest form of a post renderer which replaces predefined environment values could look like this:

#!/bin/sh
envsubst <&0

Note this will replace every occurance of $<VARNAME> which could collide with variables in the templates like shell scripts in liveness probes. So better explicitly define the variables you want to get replaced: envsubst '${USERNAME} ${PASSWORD}' <&0

Define your env variables in the shell:

export USERNAME=john PASSWORD=my-secret

In the tempaltes (e.g. secret.yaml) use the values defined in the values.yaml:

apiVersion: v1
kind: Secret
metadata:
    name: {{ .Release.Name }}-auth
data:
    username: {{ .Values.username }}
    password: {{ .Values.password }}

Note that you can not apply string transformations like b64enc on the strings as the get injected in the manifest after Helm has already processed all YAML files. Instead you can encode them in the post renderer if required.

In the values.yaml use the variable placeholders:

...
username: ${USERNAME}
password: ${PASSWORD}

The parameter --post-renderer is supported in several Helm commands e.g.

helm install --dry-run --post-renderer ./my-post-renderer.sh my-chart

By using the post renderer the variables/placeholders automatically get replaced by envsubst without additional scripting.

Björn Kraus
  • 121
  • 1
  • 4
10

i guess the question is how to lookup for env variable inside chart by looking at the env variables it-self and not by passing this with --set.

for example: i have set a key "my_db_password" and want to change the values by looking at the value in env variable is not supported.

I am not very sure on GO template, but I guess this is disabled as what they explain in helm documentation. "We removed two for security reasons: env and expandenv (which would have given chart authors access to Tiller’s environment)." https://helm.sh/docs/developing_charts/#know-your-template-functions

Alessandro Dentella
  • 1,250
  • 2
  • 16
  • 30
7

I think one simple way is just set the value directly. for example, in your Values.yml, you want pass the service name:

...
myapp:
  service:
    name: ""
...

Your service.yml just use this value as usual:

{{ .Values.myapp.service.name }}

Then to set the value, use --set, like: --set myapp.service.name=hello


Then, for example, if you want to use the environment variable, do export before that:

#set your env variable 
export MYAPP_SERVICE=hello

#pass it to helm
helm install myapp --set myapp.service.name=$MYAPP_SERVICE.

If you do debug like:

helm install myapp --set myapp.service.name=$MYAPP_SERVICE --debug --dry-run ./myapp

You can see this information at the beginning of your yml which your "hello" was set.

USER-SUPPLIED VALUES:
myapp:
  service:
    name: hello
WUJ
  • 1,663
  • 3
  • 15
  • 21
5

As an alternative to pass local environment variables, I like to store these kind of sensitive values in a folder ignored by your VCS, and use Helm .Files object to read them and provide the values to your templates.

In my opinion, the advantage is that it doesn't require the host that will operate the Helm chart to set any OS specific environment variable, and makes the chart self-contained whilst not exposing these values.

# In a folder not committed, e.g. <chart_base_directory>/secrets
username: app-username
password: 28sin47dsk9ik

Then in your chart templates:

# In deployment.yaml file
---
apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-auth
stringData::
{{ .Files.Get "<chart_base_directory>/secrets" | indent 2 }}

As a result, everything the Chart needs is accessible from within the directory where you define everything else. And instead of setting system-wide env vars, it just needs a file.

This file can be generated automatically, or copied from a committed template with dummy values. Helm will also fire an error early on install/update if this isn't defined, as opposed to creating your secret with username="" and password="" if your env vars haven't been defined, which only becomes obvious once your changes are applied to the cluster.

Walid Natat
  • 94
  • 1
  • 2