Skip to content

Instantly share code, notes, and snippets.

@bvis
Last active April 12, 2024 04:21
Show Gist options
  • Star 63 You must be signed in to star a gist
  • Fork 16 You must be signed in to fork a gist
  • Save bvis/b78c1e0841cfd2437f03e20c1ee059fe to your computer and use it in GitHub Desktop.
Save bvis/b78c1e0841cfd2437f03e20c1ee059fe to your computer and use it in GitHub Desktop.
Docker Env Vars expanded with secrets content

Variables by Secrets

Sample script that allows you to define as environment variables the name of the docker secret that contains the secret value. It will be in charge of analyze all the environment variables searching for the placeholder to substitute the variable value by the secret.

Usage

You can define the next environment variables:

$ env | grep DB_
DB_HOST=my-db-host
DB_USER=my-db-user
DB_PASS=my-db-pass

And nothing would happen. None of the variables would be modified when starting the container.

But if you define variables with the defined placeholder it will expand the value with the referred secret.

Example

Create Secret

echo "my-db-pass" | docker secret create secret-db-pass -
$ env | grep DB_
DB_HOST=my-db-host
DB_USER=my-db-user
DB_PASS={{DOCKER-SECRET:secret-db-pass}}

When starting the script will search for the placeholder {{DOCKER-SECRET:xxxx}} on each environment variable and will substitute the value by the content of the secret xxxx, in this example it means to end up with:

DB_HOST=my-db-host
DB_USER=my-db-user
DB_PASS=my-db-pass

How to use it

If you want to use this feature on any image just add the env_secrets_expand.sh file in your container entrypoint script and invoke it with source env_secrets_expand.sh

How to test this

Build a sample image with the required dependency and enter into it:

docker run --rm -v $PWD:/test -it alpine sh

Just emulate the creation of a secret and the example variables with the next commands:

mkdir -p /run/secrets/
echo "my-db-pass" > /run/secrets/secret-db-pass
export DB_HOST=my-db-host
export DB_USER=my-db-user
export DB_PASS={{DOCKER-SECRET:secret-db-pass}}

Execute the script:

ENV_SECRETS_DEBUG=true /test/env_secrets_expand.sh
#!/bin/sh
: ${ENV_SECRETS_DIR:=/run/secrets}
env_secret_debug()
{
if [ ! -z "$ENV_SECRETS_DEBUG" ]; then
echo -e "\033[1m$@\033[0m"
fi
}
# usage: env_secret_expand VAR
# ie: env_secret_expand 'XYZ_DB_PASSWORD'
# (will check for "$XYZ_DB_PASSWORD" variable value for a placeholder that defines the
# name of the docker secret to use instead of the original value. For example:
# XYZ_DB_PASSWORD={{DOCKER-SECRET:my-db.secret}}
env_secret_expand() {
var="$1"
eval val=\$$var
if secret_name=$(expr match "$val" "{{DOCKER-SECRET:\([^}]\+\)}}$"); then
secret="${ENV_SECRETS_DIR}/${secret_name}"
env_secret_debug "Secret file for $var: $secret"
if [ -f "$secret" ]; then
val=$(cat "${secret}")
export "$var"="$val"
env_secret_debug "Expanded variable: $var=$val"
else
env_secret_debug "Secret file does not exist! $secret"
fi
fi
}
env_secrets_expand() {
for env_var in $(printenv | cut -f1 -d"=")
do
env_secret_expand $env_var
done
if [ ! -z "$ENV_SECRETS_DEBUG" ]; then
echo -e "\n\033[1mExpanded environment variables\033[0m"
printenv
fi
}
env_secrets_expand
@ATLJLawrie
Copy link

This is a fantastic script that my team has put into use, but you may want to consider another character other then curly braces. At the moment there isn't a way to escape them in a compose file and in my testing they were ignored even if passed in as an environmental variable on the docker-compose call

@rbdiang
Copy link

rbdiang commented Jan 11, 2018

Agreed this is a useful script, and ran into same problem deploying service from docker-compose file. As a work around, I tweaked environment variable convention to use -> instead of {{...}}. As the example below:

version: "3.3"
services:
  simple:
    image: simple
    environment:
      - GREET=DOCKER-SECRET->greeting
      - NAME=Brian
    secrets:
       - greeting
secrets:
   greeting:
     external: true

For this change line 29 in env_secrets_expand.sh was changed to the following:

     if secret_name=$(expr match "$val" "DOCKER-SECRET->\([^}]\+\)$"); then

@rbdiang
Copy link

rbdiang commented Jan 11, 2018

Complete KISS example is here:
https://github.com/rbdiang/docker-secrets-example

@devfelipereis
Copy link

devfelipereis commented Mar 4, 2018

I changed mine.

https://gist.github.com/devfelipereis/c31dba17bf48150137761097c4c6637f

Mine will work a little bit different... For example, I have a lot of laravel apps and I have a .env like this:

DB_HOST=127.0.0.1
DB_USER=someuser
DB_PASS=somepass
and more...

I don't want to create a secret for each field and I don't want to write {{DOCKER-SECRET:secret-db-pass}} in my compose files.
I want all this in one secret... one secret for each application.

What I did, I changed the code to read a secret file and set each line as a env variable... and it works!

All I need to do is:

Your application will read the env as usual and you don't need to define the env in your compose files.. Of course, you can still set stuff that don't need to be secret... like APP_ENV=production in your compose.

I'm using with Rancher to deploy my apps and it works good 👍

@tafaust
Copy link

tafaust commented Mar 24, 2022

Stupid question, but doesn't this defeat the purpose of secrets in the first place? The /run/secrets/* are mounts from the in-memory filesystem according to the docker docs.

https://docs.docker.com/engine/swarm/secrets/#how-docker-manages-secrets

Meaning that the secrets are actively exposed during runtime.

@marlluslustosa
Copy link

I changed mine.

https://gist.github.com/devfelipereis/c31dba17bf48150137761097c4c6637f

Mine will work a little bit different... For example, I have a lot of laravel apps and I have a .env like this:

DB_HOST=127.0.0.1
DB_USER=someuser
DB_PASS=somepass
and more...

I don't want to create a secret for each field and I don't want to write {{DOCKER-SECRET:secret-db-pass}} in my compose files. I want all this in one secret... one secret for each application.

What I did, I changed the code to read a secret file and set each line as a env variable... and it works!

All I need to do is:

* Create a secret. I'm using my app name as the secret name(ez to find and use)

* Set in my compose **SECRET_NAME=thesecretname** as an env variable. So the script can find your secret file.

* Put that script in your entrypoint(https://gist.github.com/devfelipereis/c31dba17bf48150137761097c4c6637f).

Your application will read the env as usual and you don't need to define the env in your compose files.. Of course, you can still set stuff that don't need to be secret... like APP_ENV=production in your compose.

I'm using with Rancher to deploy my apps and it works good +1

Thanks for the code!

Improvements i made:

  • The variables secret_name and secret_file_path were declared as local variables within the set_env_secrets() function, reducing their scope and avoiding conflicts with other variables in the environment.
  • The -n expression was used instead of ! -z to check if the ENV_SECRETS_DEBUG variable is defined and not empty.
  • The declaration of the IFS variable was modified to IFS= read -r line || [ -n "$line" ], avoiding issues with interpreting values containing spaces and ensuring that the loop is executed even if the last line doesn't have a line break.

https://gist.github.com/marlluslustosa/0240f89e2d8ffa14c23ca77fcbda39a3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment