Inject secrets into Docker containers using SecretHub

This guide will help you inject secrets as environment variables into containers at runtime.

This means secrets do not have to be baked into images, mounted as volumes, or hardcoded into scripts and command-line flags.

A quick overview

There is no one way to inject secrets as environment variables into Docker containers. There are quite a few options of which we will highlight two in this post.

  1. Pass secrets as environment variables to containers
  2. Pass secrets in Docker Compose files

First some setup

To follow along, you will need a couple of things:

  • You’ll need to have installed the SecretHub CLI (v0.14 or higher) and successfully signed up for an account. If you haven’t yet done so or you don’t have an up-to-date client, see Getting Started.
  • You have a working knowledge of Docker and know your way around their documentation.
  • The guide uses Docker v18.03.1 and Docker Compose v1.21.2 on a Unix system. To get it to work with other versions may require some engineering effort.

The example commands use the SECRETHUB_USERNAME environment variable to make them copy-pastable. You can easily set the variable by executing the following command:

echo "What is your SecretHub username?"
read SECRETHUB_USERNAME

To setup the docker-examples repository, you can run the following commands:

secrethub repo init ${SECRETHUB_USERNAME}/docker-examples
echo "user1" | secrethub write ${SECRETHUB_USERNAME}/docker-examples/db_user
secrethub generate rand ${SECRETHUB_USERNAME}/docker-examples/db_password

1. Pass secrets as environment variables to containers

Setting environment variables to secrets from SecretHub can be done very easily by using the secrethub run command.

This can be combined with using the -e or --env flag in Docker. When using this flag, you can only declare the name part and leave out the value part to make Docker use the value of the host environment, in other words, -e VAR versus. -e VAR=value. We will use this to let SecretHub set those environment variables

To populate the host environment with the right secrets, you can wrap the docker run command in a secrethub run command. Similar to Docker, the secrethub run command supports setting environment variables with an -e or --env flag. SecretHub’s --env flag takes a VAR=<path> value where <path> points to the secret inside a SecretHub repository.

secrethub run \
    -e DB_USER=${SECRETHUB_USERNAME}/docker-examples/db_user \
    -e DB_PASSWORD=${SECRETHUB_USERNAME}/docker-examples/db_password -- \
    docker run -e DB_USER -e DB_PASSWORD alpine env

To make life even easier, you can also use the --template flag with secrethub run to source environment variables from a template file. These templates are YAML-style files where key-value pair represents an environment variable to set. All occurrences of a ${<path>} are replaced with their corresponding secret values in SecretHub. See the reference documentation for more details.

Use this secrets.env.tpl template file (make sure to fill in your username correctly):

DB_USER: ${<YOUR-SECRETHUB-USERNAME>/docker-examples/db_user}
DB_PASSWORD: ${<YOUR-SECRETHUB-USERNAME>/docker-examples/db_password}

Executing the following secrethub run command injects secrets and configures them as environment variables that are then passed to the docker run command:

secrethub run --template secrets.env.tpl -- \
    docker run -e DB_USER -e DB_PASSWORD alpine env

This allows you to precisely codify which secrets are used by what containers in a plaintext template file, without changing the container. You do have to explicitly tell Docker about the environment variables it should pass along though.


2. Pass secrets in Docker Compose files

The same techniques can be applied to docker-compose to inject secrets as environment variables. Similar to the command-line, you need to define which variables Docker should source from the host environment in the Compose file:

version: '3'

services:
  webserver:
    image: alpine
    command: env
    environment:
      - DB_USER
      - DB_PASSWORD

Next, secrets can be injected by wrapping the docker-compose command with the same secrethub run command:

secrethub run \
    -e DB_USER=${SECRETHUB_USERNAME}/docker-examples/db_user \
    -e DB_PASSWORD=${SECRETHUB_USERNAME}/docker-examples/db_password \
    -- docker-compose up

Finally, using the template we’ve defined earlier, this can all be simplified to:

secrethub run --template secrets.env.tpl -- \
    docker-compose up

Everything is defined in non-sensitive files with only the variable names duplicated across multiple files.

If some more customization is needed, it is also possible to access the secret values directly in the Compose file. Take the following case in which a value from SecretHub is appended to a static string:

version: '3'

services:
  webserver:
    image: alpine
    command: env
    environment:
      - DB_NAME=datbase-for-${DB_USER}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}

Note that Docker Compose also sources environment variables from a .env file located next to the docker-compose.yml file. secrethub inject and a template can be used to generate a .env file populated with the secrets. However, this is not preferred in most cases as this writes a file with secrets to the file-system.


Wrapping up

That’s it! You now know the basics of injecting secrets as environment variables in Docker containers and how to use that in Docker Compose.

This should help you avoid checking sensitive files into source control or baking secrets into images.

In the coming weeks, we’ll publish a fully working Docker example where we use those values for an actual web server.

Happy coding!

P.S. if anything is unclear or needs changing, please reach out.

References

  • Vladislav Supalov has written an excellent guide on how to configure Docker environment variables correctly.

Note that environment variables can be read by any user on the host system that has access to the docker user group, e.g., by using the docker inspect command. However, when an unauthorized user gains this type of access on the Docker host, ensuring the secrecy of your container’s environment variables is probably the least of your worries.