231

I've been experimenting with Docker recently on building some services to play around with and one thing that keeps nagging me has been putting passwords in a Dockerfile. I'm a developer so storing passwords in source feels like a punch in the face. Should this even be a concern? Are there any good conventions on how to handle passwords in Dockerfiles?

Mark O'Connor
  • 76,015
  • 10
  • 139
  • 185
anthonator
  • 4,915
  • 7
  • 36
  • 50
  • 8
    There is an open issue on Github requesting for best practices regarding Docker and secrets, the issue is here: https://github.com/docker/docker/issues/13490 – Luís Bianchin Dec 10 '15 at 14:00

14 Answers14

159

Definitely it is a concern. Dockerfiles are commonly checked in to repositories and shared with other people. An alternative is to provide any credentials (usernames, passwords, tokens, anything sensitive) as environment variables at runtime. This is possible via the -e argument (for individual vars on the CLI) or --env-file argument (for multiple variables in a file) to docker run. Read this for using environmental with docker-compose.

Using --env-file is definitely a safer option since this protects against the secrets showing up in ps or in logs if one uses set -x.

However, env vars are not particularly secure either. They are visible via docker inspect, and hence they are available to any user that can run docker commands. (Of course, any user that has access to docker on the host also has root anyway.)

My preferred pattern is to use a wrapper script as the ENTRYPOINT or CMD. The wrapper script can first import secrets from an outside location in to the container at run time, then execute the application, providing the secrets. The exact mechanics of this vary based on your run time environment. In AWS, you can use a combination of IAM roles, the Key Management Service, and S3 to store encrypted secrets in an S3 bucket. Something like HashiCorp Vault or credstash is another option.

AFAIK there is no optimal pattern for using sensitive data as part of the build process. In fact, I have an SO question on this topic. You can use docker-squash to remove layers from an image. But there's no native functionality in Docker for this purpose.

You may find shykes comments on config in containers useful.

kalm42
  • 784
  • 9
  • 19
Ben Whaley
  • 32,811
  • 7
  • 87
  • 85
  • As noted in other comments there will be 2 layers (after ADD and after first RUN) that contain `.config` file. – Petr Gladkikh Oct 09 '14 at 12:14
  • 1
    Yer, env variables seems the best way to go. I've been looking at this in the context of TDDing Dockerfile development. – gnoll110 Mar 09 '15 at 04:59
  • 5
    I'm concerned that if your password is an env variable, it appears in `docker inspect`. – slim Aug 26 '16 at 09:25
  • A default installation of docker (on linux) requires sudoer privileges to run `docker inspect`. If the attacker can already sudo, snatching your password out of docker inspect is probably pretty low on your list of things that can now go wrong. This particular detail seems like acceptable risk to me. – GrandOpener Jun 01 '17 at 17:42
  • 8
    @GrandOpener That only applies to the situation where there is an attacker using your system. If I push a docker image to a repository, and it's pulled by someone else, I don't care if they have sudo on their own system but I definitely care if they see secrets in env that are no longer supposed to be there. – vee_ess Jun 22 '17 at 14:22
  • 2
    Isnt one of the problems with environment params that they intrinsically get copied to all descendent's, including grand-child processes? Thus a script or a plugin used in your docker container would have access to these passwords by accident? – Groostav May 28 '19 at 19:33
  • If we use vault then where to store the vault secret id and role id which is used by the application to fetch credentialsmemory. How can we pass that into docker? – Swapnil Gangrade Jul 29 '21 at 14:37
107

Our team avoids putting credentials in repositories, so that means they're not allowed in Dockerfile. Our best practice within applications is to use creds from environment variables.

We solve for this using docker-compose.

Within docker-compose.yml, you can specify a file that contains the environment variables for the container:

 env_file:
- .env

Make sure to add .env to .gitignore, then set the credentials within the .env file like:

SOME_USERNAME=myUser
SOME_PWD_VAR=myPwd

Store the .env file locally or in a secure location where the rest of the team can grab it.

See: https://docs.docker.com/compose/environment-variables/#/the-env-file

mennanov
  • 1,195
  • 3
  • 16
  • 27
theUtherSide
  • 3,338
  • 4
  • 36
  • 35
  • 21
    You can also do this without a .env file, if you wish. Just use the [environment property](https://docs.docker.com/compose/compose-file/#environment) in your docker-compose.yml file . _"Environment variables with only a key are resolved to their values on the machine Compose is running on, which can be helpful for secret or host-specific values."_ – D. Visser Feb 03 '16 at 11:41
  • 1
    give this man a cookie ! :) yep this is really good practice I just want to add that https://docs.docker.com/compose/env-file/ this should work automatically but in docker compose version 2 it seems you need to declare it as described in this answer. – equivalent8 May 23 '16 at 12:44
  • 6
    Using environment variables is discouraged by the Docker team itself, as env var can be seen through /proc//environ and docker inspect. It only obfuscate the way to get the credentials for an attacker who has gained root access. Of course credentials should never be tracked by the CVS. I guess the only way to prevent a root-user to get cred is to read the credentials from within the web app (hoping that it does not update its proc environ file) from an encrypted file, the decryption process securely asking for a password. I think I'm gonna try with a tomb: https://github.com/dyne/Tomb – pawamoy Feb 06 '18 at 19:10
  • `.gitignore` so that the `.env` file with sensitive information does not get checked-in to GitHub. I'm pretty sure this won't work if you add it `.dockerignore` – theUtherSide Feb 22 '19 at 01:17
  • hi @theUtherSide, thanks for your answer, I had a question, when i dont checkin the `.env` file and I do a deployment to a server on premise, do you suggest to create the `.env` file again on server manually? – opensource-developer Jul 02 '19 at 14:00
  • I would not recommend manually creating a `.env` file on a server...or doing anything manually on server for that matter. There are many ways/tools for this type of thing. – theUtherSide Jul 10 '19 at 00:18
  • 3
    @theUtherSide it would be great if you can give us any examples or links of the "ways/tools" you meant. – fsevenm May 17 '21 at 07:34
40

Docker now (version 1.13 or 17.06 and higher) has support for managing secret information. Here's an overview and more detailed documentation

Similar feature exists in kubernetes and DCOS

P.J
  • 472
  • 2
  • 9
Heather QC
  • 680
  • 8
  • 11
  • 1
    Some useful commands from above links: `docker secret create` : create a secret `docker secret inspect` : display detailed information about a secret `docker secret ls`: view all secrets `docker secret rm` : remove a specific secret `--secret` flag for `docker service create`: create a secret during service creation `--secret-add` and `--secret-rm` flags for `docker service update`: update the value of a secret or remove a secret during service update task. Docker secrets are protected at rest on manager nodes and provisioned to worker nodes when during container startup. – P.J Aug 16 '17 at 15:05
  • 16
    Yes, you need to set up a swarm to use Docker secrets – Heather QC Sep 09 '17 at 22:46
  • 20
    This is a good start on an answer, but needs much more information from that which is linked to appear in the answer itself. – Jeff Lambert Oct 20 '17 at 14:48
  • 22
    Not sure this can be the accepted answer if it only works with swarms. Many people aren't using swarms, but still need to pass secrets. – John Y Jan 14 '19 at 10:33
  • Docker Compose can manage secrets without a swarm. See [this answer](https://stackoverflow.com/a/48460539/5096199). – Robin Dinse Jan 07 '22 at 10:40
  • This is not very helpful as most of the out-of-the-box images expect passwords to be passed with environment variables – Sebi2020 Feb 12 '22 at 13:47
9

You should never add credentials to a container unless you're OK broadcasting the creds to whomever can download the image. In particular, doing and ADD creds and later RUN rm creds is not secure because the creds file remains in the final image in an intermediate filesystem layer. It's easy for anyone with access to the image to extract it.

The typical solution I've seen when you need creds to checkout dependencies and such is to use one container to build another. I.e., typically you have some build environment in your base container and you need to invoke that to build your app container. So the simple solution is to add your app source and then RUN the build commands. This is insecure if you need creds in that RUN. Instead what you do is put your source into a local directory, run (as in docker run) the container to perform the build step with the local source directory mounted as volume and the creds either injected or mounted as another volume. Once the build step is complete you build your final container by simply ADDing the local source directory which now contains the built artifacts.

I'm hoping Docker adds some features to simplify all this!

Update: looks like the method going forward will be to have nested builds. In short, the dockerfile would describe a first container that is used to build the run-time environment and then a second nested container build that can assemble all the pieces into the final container. This way the build-time stuff isn't in the second container. This of a Java app where you need the JDK for building the app but only the JRE for running it. There are a number of proposals being discussed, best to start from https://github.com/docker/docker/issues/7115 and follow some of the links for alternate proposals.

TvE
  • 1,016
  • 1
  • 11
  • 19
7

An alternative to using environment variables, which can get messy if you have a lot of them, is to use volumes to make a directory on the host accessible in the container.

If you put all your credentials as files in that folder, then the container can read the files and use them as it pleases.

For example:

$ echo "secret" > /root/configs/password.txt
$ docker run -v /root/configs:/cfg ...

In the Docker container:

# echo Password is `cat /cfg/password.txt`
Password is secret

Many programs can read their credentials from a separate file, so this way you can just point the program to one of the files.

Malvineous
  • 25,144
  • 16
  • 116
  • 151
  • 1
    This is a bad idea. especially typing in passwords into bash. All your secrets get stored into bash_history. – Sebi2020 Feb 12 '22 at 13:48
  • Yes that's true, but I was only using it as an example of what the file contents should look like. You can use a text editor or other program to create the file. However if you are running on a system where your `.bash_history` could be compromised, you shouldn't be putting your passwords in at all, whether in files, environment variables or on the command line. Use a secure system when dealing with passwords like this. – Malvineous Feb 13 '22 at 05:23
7

run-time only solution

docker-compose also provides a non-swarm mode solution (since v1.11: Secrets using bind mounts).

The secrets are mounted as files below /run/secrets/ by docker-compose. This solves the problem at run-time (running the container), but not at build-time (building the image), because /run/secrets/ is not mounted at build-time. Furthermore this behavior depends on running the container with docker-compose.


Example:

Dockerfile

FROM alpine
CMD cat /run/secrets/password

docker-compose.yml

version: '3.1'
services:
  app:
    build: .
    secrets:
      - password

secrets:
  password:
    file: password.txt

To build, execute:

docker-compose up -d

Further reading:

Murmel
  • 5,402
  • 47
  • 53
  • 1
    "but not at build-time (building the image)," and then your example is for build-time! If you would use the mounted secret-file in the CMD pragma, then it would make sense. – lnksz Nov 03 '21 at 14:35
  • @lnksz Thanks, I adapted the example – Murmel Nov 03 '21 at 14:46
  • This only worked with a prefix like this: `DOCKER_BUILDKIT=1 docker-compose up -d` – Robin Dinse Jan 07 '22 at 10:57
2

My approach seems to work, but is probably naive. Tell me why it is wrong.

ARGs set during docker build are exposed by the history subcommand, so no go there. However, when running a container, environment variables given in the run command are available to the container, but are not part of the image.

So, in the Dockerfile, do setup that does not involve secret data. Set a CMD of something like /root/finish.sh. In the run command, use environmental variables to send secret data into the container. finish.sh uses the variables essentially to finish build tasks.

To make managing the secret data easier, put it into a file that is loaded by docker run with the --env-file switch. Of course, keep the file secret. .gitignore and such.

For me, finish.sh runs a Python program. It checks to make sure it hasn't run before, then finishes the setup (e.g., copies the database name into Django's settings.py).

Malakai
  • 3,011
  • 9
  • 35
  • 49
2

There is a new docker command for "secrets" management. But that only works for swarm clusters.

docker service create
--name my-iis
--publish target=8000,port=8000
--secret src=homepage,target="\inetpub\wwwroot\index.html"
microsoft/iis:nanoserver 
MadMike
  • 1,391
  • 1
  • 16
  • 38
José Ibañez
  • 652
  • 8
  • 10
2

The issue 13490 "Secrets: write-up best practices, do's and don'ts, roadmap" just got a new update in Sept. 2020, from Sebastiaan van Stijn:

Build time secrets are now possible when using buildkit as builder; see the blog post "Build secrets and SSH forwarding in Docker 18.09", Nov. 2018, from Tõnis Tiigi.

The documentation is updated: "Build images with BuildKit"

The RUN --mount option used for secrets will graduate to the default (stable) Dockerfile syntax soon.

That last part is new (Sept. 2020)

New Docker Build secret information

The new --secret flag for docker build allows the user to pass secret information to be used in the Dockerfile for building docker images in a safe way that will not end up stored in the final image.

id is the identifier to pass into the docker build --secret.
This identifier is associated with the RUN --mount identifier to use in the Dockerfile.
Docker does not use the filename of where the secret is kept outside of the Dockerfile, since this may be sensitive information.

dst renames the secret file to a specific file in the Dockerfile RUN command to use.

For example, with a secret piece of information stored in a text file:

$ echo 'WARMACHINEROX' > mysecret.txt

And with a Dockerfile that specifies use of a BuildKit frontend docker/dockerfile:1.0-experimental, the secret can be accessed.

For example:

# syntax = docker/dockerfile:1.0-experimental
FROM alpine

# shows secret from default secret location:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

# shows secret from custom secret location:
RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar

This Dockerfile is only to demonstrate that the secret can be accessed. As you can see the secret printed in the build output. The final image built will not have the secret file:

$ docker build --no-cache --progress=plain --secret id=mysecret,src=mysecret.txt .
...
#8 [2/3] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#8       digest: sha256:5d8cbaeb66183993700828632bfbde246cae8feded11aad40e524f54ce7438d6
#8         name: "[2/3] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret"
#8      started: 2018-08-31 21:03:30.703550864 +0000 UTC
#8 1.081 WARMACHINEROX
#8    completed: 2018-08-31 21:03:32.051053831 +0000 UTC
#8     duration: 1.347502967s
#9 [3/3] RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar
#9       digest: sha256:6c7ebda4599ec6acb40358017e51ccb4c5471dc434573b9b7188143757459efa
#9         name: "[3/3] RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar"
#9      started: 2018-08-31 21:03:32.052880985 +0000 UTC
#9 1.216 WARMACHINEROX
#9    completed: 2018-08-31 21:03:33.523282118 +0000 UTC
#9     duration: 1.470401133s
...
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
1

The 12-Factor app methodology tells, that any configuration should be stored in environment variables.

Docker compose could do variable substitution in configuration, so that could be used to pass passwords from host to docker.

Bunyk
  • 7,635
  • 8
  • 47
  • 79
1

Starting from Version 20.10, besides using secret-file, you could also provide secrets directly with env.

buildkit: secrets: allow providing secrets with env moby/moby#41234 docker/cli#2656 moby/buildkit#1534

  • Support --secret id=foo,env=MY_ENV as an alternative for storing a secret value to a file.
  • --secret id=GIT_AUTH_TOKEN will load env if it exists and the file does not.

secret-file:

THIS IS SECRET

Dockerfile:

# syntax = docker/dockerfile:1.3
FROM python:3.8-slim-buster
COPY build-script.sh .
RUN --mount=type=secret,id=mysecret ./build-script.sh

build-script.sh:

cat /run/secrets/mysecret

Execution:

$ export MYSECRET=theverysecretpassword
$ export DOCKER_BUILDKIT=1
$ docker build --progress=plain --secret id=mysecret,env=MYSECRET -t abc:1 . --no-cache
......
#9 [stage-0 3/3] RUN --mount=type=secret,id=mysecret ./build-script.sh
#9 sha256:e32137e3eeb0fe2e4b515862f4cd6df4b73019567ae0f49eb5896a10e3f7c94e
#9 0.931 theverysecretpassword#9 DONE 1.5s
......
atline
  • 28,355
  • 16
  • 77
  • 113
0

With Docker v1.9 you can use the ARG instruction to fetch arguments passed by command line to the image on build action. Simply use the --build-arg flag. So you can avoid to keep explicit password (or other sensible information) on the Dockerfile and pass them on the fly.

source: https://docs.docker.com/engine/reference/commandline/build/ http://docs.docker.com/engine/reference/builder/#arg

Example:

Dockerfile

FROM busybox
ARG user
RUN echo "user is $user"

build image command

docker build --build-arg user=capuccino -t test_arguments -f path/to/dockerfile .

during the build it print

$ docker build --build-arg user=capuccino -t test_arguments -f ./test_args.Dockerfile .

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM busybox
 ---> c51f86c28340
Step 2 : ARG user
 ---> Running in 43a4aa0e421d
 ---> f0359070fc8f
Removing intermediate container 43a4aa0e421d
Step 3 : RUN echo "user is $user"
 ---> Running in 4360fb10d46a
**user is capuccino**
 ---> 1408147c1cb9
Removing intermediate container 4360fb10d46a
Successfully built 1408147c1cb9

Hope it helps! Bye.

NickGnd
  • 5,107
  • 1
  • 20
  • 26
  • 29
    According to [Docker's ARG docs](https://docs.docker.com/engine/reference/builder/#arg): "It is not recommended to use build-time variables for passing secrets like github keys, user credentials etc" – Lie Ryan Dec 10 '15 at 04:46
  • 3
    Just wondering why Docker disrecommends to use `--build-arg var=secret` for passing a SSH private key into an image, there is no rationale documented. Can anyone explain it? – Henk Wiersema Oct 06 '16 at 06:23
  • 2
    @HenkWiersema Process information, logs and command history are insecure. Process information is available publicly and that includes all command line parameters. Frequently these calls end up in logs which can be public also. It's not uncommon for an attacker to inspect information on running processes and trawl public logfiles for secrets. Even when it's not public, it could be stored in your command history which would make it easy for someone to get secrets through a non-administrative account. – tudor -Reinstate Monica- Dec 13 '16 at 00:42
  • 2
    What is the recommended way to supply credentials that are needed at build time? For example, an image that needs aws s3 access to fetch a large data set that will reside inside the image? – ely May 03 '17 at 14:38
  • 6
    I imagine the reason it is not recommended is because `docker history` exposes `build-arg`/`ARG` variables. One can pull any image, inspect it and see any secrets passed during build as a `build-arg`/`ARG` parameter. – vee_ess Jun 21 '17 at 08:33
  • 4
    Do not use this method! It will leak sensitive information on docker history – aug70co Jan 10 '19 at 19:59
0

Something simply like this will work I guess if it is bash shell.

read -sp "db_password:" password | docker run -itd --name <container_name> --build-arg mysql_db_password=$db_password alpine /bin/bash

Simply read it silently and pass as argument in Docker image. You need to accept the variable as ARG in Dockerfile.

-5

While I totally agree there is no simple solution. There continues to be a single point of failure. Either the dockerfile, etcd, and so on. Apcera has a plan that looks like sidekick - dual authentication. In other words two container cannot talk unless there is a Apcera configuration rule. In their demo the uid/pwd was in the clear and could not be reused until the admin configured the linkage. For this to work, however, it probably meant patching Docker or at least the network plugin (if there is such a thing).

Richard
  • 10,122
  • 10
  • 42
  • 61