Docker logo

Secret Management for Docker

The easiest way to get secrets into your Docker app is to bake them into your image. But when doing so, not only are you sacrificing security, you’ll also need to build separate images for all your environments. On top of that, rotating a secret would need to trigger a rebuild of all those images. Yuck.

This guide will help you to inject secrets into your Dockerized app without compromising either security or ease of implementation.

To do this, we’ll use the run command from the SecretHub CLI, which injects secrets into your app as environment variables.

You can use the run command with Docker in two ways:

  • Wrap the docker run command
  • Wrap your app start command

Before you begin

Before you start, make sure you have completed the following steps:

  1. Install the SecretHub CLI for your OS.
  2. Sign up for a SecretHub account.
  3. To demonstrate the concept, we’ll use the demo secrets. If you haven’t done so already, create them in your workspace by running:
    secrethub demo init
    
  4. Install Docker

Option 1: Wrap docker run

Environment variables can be set in a Docker container using the -e flag:

docker run -e PASSWORD=cD5bUGTAL2ygEu4IqXkS alpine env

Now, to avoid hardcoding this secret, you can load them from SecretHub instead by wrapping the command in secrethub run and specifying a reference to the secret, instead of the secret value.

Then, tell Docker to forward the environment variable by setting the -e flag without a value:

secrethub run -e PASSWORD=your-username/demo/password -- docker run -e PASSWORD alpine env

Downsides

While this approach works fine to quickly get some secrets into a Docker container, it does come with several downsides.

First of all, you’ll have to be aware that environment variables passed into a Docker container can be read out using docker inspect. Just this one-liner can unintentionally spill your secrets:

docker inspect -f '{{range $index, $value := .Config.Env}}{{println $value}}{{end}}' <container id>

Secondly, if you use an orchestrator to schedule your Docker containers, wrapping docker run won’t be an option since the orchestrator has now taken on the task of running the containers for you.

Finally, it also kind of breaks the core Docker principle of isolation, by placing software to run the application outside of the container.

Fortunately, there’s a better way.

Option 2: Wrap your app start command

While the previous option works fine when locally running a Docker container, the recommended way would be to take the secrethub run command a level down: make it wrap your app start command instead.

By including the CLI and secrethub.env in your Docker image, it’ll work anywhere, and with any orchestrator that supports Docker.

Let’s look at a simple Alpine Dockerfile for a Node.js app as an example:

FROM node:alpine

COPY . .
RUN npm install

CMD ["npm", "start"]

To inject secrets into this app, use apk to install the CLI onto the image and change the entrypoint to secrethub run:

FROM node:alpine
RUN apk add --repository https://alpine.secrethub.io/alpine/edge/main --allow-untrusted secrethub-cli

COPY . .
RUN npm install

ENTRYPOINT ["secrethub", "run", "--"]
CMD ["npm", "start"]

This method provides a good trade-off between security and ease of implementation. You do have to install a lightweight binary onto your image, but there’s no need to integrate a client SDK of some sort in your application code. Your app just reads out environment variables, not knowing anything about SecretHub.

Also, Docker does not know about the environment variables injected this way, as they are now tied to a child process. This also means that docker inspect can not get to it anymore, and any automated logging tool that uses it.

Diagram showing environments when running secrethub in Docker

See also