Setting docker env var from build secret

Viewed 2847

Is there a way to set environment variables from the new docker build enhancements?

Have tried

RUN --mount=type=secret,id=secret export SECRET=`/run/secrets/secret
RUN --mount=type=secret,id=secret ENV SECRET=`/run/secrets/secret

Both doesn't work. Or is setting secrets on environment variables on dockerfile bad? Since running docker history to the env var being set in plain text. If that's the case, what's the best way to set the env var as secured as possible?

2 Answers

To set a variable from a secret, you can use the $(cat /filename) syntax in shell. This affects the shell within that single step, so all of your uses of that variable need to be within the same step. You cannot extract a variable from a RUN step into an ENV step. If you need it to persist to other RUN steps, you would need to write the variable to the filesystem and have in included in the image, which is undesirable (instead just mount the secret a second time in the later RUN step).

Here's a working example, you could also export that secret with export secret_var:

$ cat df.secret
FROM busybox
RUN --mount=type=secret,id=secret \
    secret_var="$(cat /run/secrets/secret)" \
 && echo ${secret_var}

$ cat secret.txt
my_secret

$ docker build --progress=plain --secret id=secret,src=$(pwd)/secret.txt -f df.secret .
#1 [internal] load build definition from df.secret
#1 sha256:85a18e77d3e60159b744d6ee3d96908a6fed0bd4f6a46d038e2aa0201a1028de
#1 DONE 0.0s

#1 [internal] load build definition from df.secret
#1 sha256:85a18e77d3e60159b744d6ee3d96908a6fed0bd4f6a46d038e2aa0201a1028de
#1 transferring dockerfile: 152B done
#1 DONE 0.0s

#2 [internal] load .dockerignore
#2 sha256:a5a676bca3eaa2c757a3ae40d8d5d5e91b980822056c5b3b6c5b3169fc65f0f1
#2 transferring context: 49B done
#2 DONE 0.0s

#3 [internal] load metadata for docker.io/library/busybox:latest
#3 sha256:da853382a7535e068feae4d80bdd0ad2567df3d5cd484fd68f919294d091b053
#3 DONE 0.0s

#5 [1/2] FROM docker.io/library/busybox
#5 sha256:08a03f3ffe5fba421a6403c31e153425ced631d108868f30e04985f99d69326e
#5 DONE 0.0s

#4 [2/2] RUN --mount=type=secret,id=secret     secret=$(cat /run/secrets/secret)  && echo ${secret}
#4 sha256:6ef91a8a7daf012253f58dba292a0bd86af1d1a33a90838b6a99aba5abd4cfaf
#4 0.587 my_secret
#4 DONE 0.7s

#6 exporting to image
#6 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00
#6 exporting layers 0.0s done
#6 writing image sha256:a52db3458ad88481406cd60627e2ed6f55b6720c1614f65fa8f453247a9aa4de done
#6 DONE 0.0s

Note the line #4 0.587 my_secret showing the secret was output.

In your examples the environment variables are being set from secrets, but just for that process.

If you want run time environment variables to be set from secrets, you'll need to either mount a volume containing the secrets, or pass the secrets via docker-compose, in either case, then utilize an entrypoint script to set the secrets based on secrets locations.

Note: the secret files will need to be available to anyone/service that will be running the dockerfile.

Add an entrypoint script to your Dockerfile

Dockerfile:

FROM alpine

# Bash is needed for the script to set env_vars based on secrets
RUN apk upgrade --update-cache --available && \
    apk add bash \
    && rm -rf /var/cache/apk/*

# Adding entrypoint script to image
COPY ./docker_entrypoint /usr/local/bin/docker_entrypoint
RUN chmod +x /usr/local/bin/docker_entrypoint

ENTRYPOINT [ "docker_entrypoint"]

CMD ["printenv"]

This script will look for an environment variables with the format ENVAR_FILE to find the path of where the secret is being stored - export the secret to an environment variable without the _FILE, and then call the remaining command.

docker_entrypoint:

#!/usr/bin/env bash

set -e

file_env() {
    local var="$1"
    local fileVar="${var}_FILE"
    local def="${2:-}"

    if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
        echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
        exit 1
    fi
    local val="$def"
    if [ "${!var:-}" ]; then
        val="${!var}"
    elif [ "${!fileVar:-}" ]; then
        val="$(< "${!fileVar}")"
    fi
    export "$var"="$val"
    unset "$fileVar"
}

# Explicitly list the environment variables you want to
# set from secrets
file_env "ENVAR1"
file_env "ENVAR2"

exec "$@"

Now you could pass environment variables of the format ENVAR_FILE=/mnt/path/to/secret - and the above entrypoint script will read the contents of that file and generate the variables ENVAR=whateverYourSecretIs

An example using docker-compose:

services:
  someService:
    image: <imageFromDockerFileAbove>
    environment: 
      - ENVAR1_FILE=/run/secrets/envar1
      - ENVAR2_FILE=/run/secrets/envar2
    secrets:
      - envar1
      - envar2
secrets:
  envar1:
    file: /pth/to/secret
  envar2:
    file: /pth/to/secret

Expected output:

someService_1  | HOSTNAME=<containerID>
someService_1  | ENVAR1=mysecret1
someService_1  | ENVAR2=mysecret2
someService_1  | PWD=/
someService_1  | HOME=/root
someService_1  | SHLVL=0
someService_1  | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

And

docker inspect --format='{{json .Config.Env}}' <containerID>

returns:

["ENVAR1_FILE=/run/secrets/envar1","ENVAR2_FILE=/run/secrets/envar2","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]

Related