Docker Secrets in multiple environments using a single Dockerfile

Your team may keep sensitive data in version control. Don’t do that. Better practice suggests that we inject our secrets into the environment in runtime instead. This allows us to keep our sensitive data in a password store, accessing it only when we need to.

The problem when we’ve many environments (dev, uat, test, qa, prod) is managing all the different Dockerfiles and secrets for each environment. Add in microservices where we multiply the number of environments by the number of services, and we get overwhelmed very quickly.

Today, we’ll show you how to solve this problem for an Enterprise level microservice architecture for 2-n environments using Docker.

There’s little overhead required to set this up, and in addition to making your security team happier, will trivialize your CI/CD pipeline by reducing the number of moving parts.

Technologies:

  • Docker
  • Docker Compose
  • Docker Secrets
  • Java (can be swapped out for any language)

This tutorial assumes you have decent understanding of Docker.

Setup

We’re launching our services in Docker containers. So we’ll create a Dockerfile for each service, and inject our secrets using the generic secret name, and then we can map an environment specific secret to it in actual runtime in our Compose files.

With this approach, we only need a single Dockerfile for all our environments.

  1. Create Docker Secrets in Each environment

Let’s log onto each environment and create the secret using Docker from cmd. Let’s pretend we have a single secret, single service, and two environments, dev and prod.

Dev

➜  echo "devpwd" | docker secret create service1-secret-DEV -
kgilm...

Prod

➜  echo "prdpwd" | docker secret create service1-secret-PRD -
w8fbl...

2. Create our Dockerfile for the microservice

Dockerfile

FROM openjdk:8-jdk

RUN mkdir -p /usr/app
WORKDIR /usr/app

COPY ./target/my-jar.jar .

EXPOSE 8181

CMD /usr/bin/java -jar \
  -Dweb.security.api.password="$(cat /run/secrets/service1-secret)" \
  ./my-jar.jar

We inject our secret using this line:

-Dweb.security.api.password=”$(cat /run/secrets/service1-secret)”

We tell Docker to inject the container with an environment variable called “web.security.api.password”. It will load it with the value of “$(cat /run/secrets/service1-secret)”.

When we add a Docker Secret to our container, it is automatically created as a file in the container under /run/secrets. The name of the file, “service1-secret” is the name of the secret.

3. Create Our Docker Compose files

Dev

version: '3.7'

services:
  my-java-service:
    image: 'my-java-service:latest'
    secrets:
      - source: service1-secret-dev
        target: service1-secret
      
secrets:
  service1-secret-dev:
    external: true

Prod

version: '3.7'

services:
  my-java-service:
    image: 'my-java-service:latest'
    secrets:
      - source: service1-secret-prd
        target: service1-secret
      
secrets:
  service1-secret-prd:
    external: true

We map the secret we created in the environment to the generic secret name being used by the Dockerfile.

That’s all! Just like that, we only need a single Dockerfile.

Was this helpful? Was anything missing? Please let me know by leaving a comment below. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *