Secure Your Docker Images with Docker Secrets

Add docker secrets to prevent your docker image from leaking passwords

Secure Your Docker Images with Docker Secrets
Keep your Docker image locked with Secrets (image by olieman.eth on Unsplash)

When you leak confidential information into your Dockerfile, you open your image up to all kinds of attackers that can steal your credentials, take control of you container, or inject malicious code into your containers. This article focusses on providing confidential information to your Dockerfile WITHOUT leaving any trace of it in the resulting image.

There will be no confidential information in your image when you follow the simple steps in this article. We’ll go through a full example and at the end you’ll have a complete Dockerfile that implements the solution. Let’s code!

TL;DR? Scroll down to “The correct way — docker secrets”
Unfamiliar with Docker? Check out this article


What problem are we solving?

Sometimes you need to provide your Dockerfile with confidential information like API keys, credentials or certificates, for example to git clone a private repository.

We need to provide this information without “baking” it into the image. If you hardcode credentials into your Dockerfile or copy your .env files into the image you open your image up for abuse; attackers can extract your credentials or gain access to your containers for example. Even if you remove a file after using it in the Dockerfile; it still exists in the image layers as we’ll find out later.

The solution: Docker secrets

Docker secrets lets you use confidential information in your Dockerfile without leaving any trace of it. We’ll get into how and why this works in the section below. First we’ll set up our demonstration.


Setup demo

For this article we have a real simple app: myapp.py that only contains one line of code: print('hello’). When we Dockerize this script our Dockerfile might look like this:

If the Dockerfile above does not seem familiar to you please first read this article about the basics of docker.

We’ll build the image with docker build . -t "my_app" and run the container with docker run my_app. This just prints “hello” on the commandline. Great! Everything works. Let’s introduce our problem.


Adding private stuff

Imagine that our python app wants to make use of a package called privatestuff that we’ve put on our private pypi. We can install this package with the pip command below.

How can we install this package in our Dockerfile?


The bad way — hardcoding

The first thing you might think of is just to hardcode your credentials in your Dockerfile. Don’t ever do this, this is just asking for problems.

As you see we’ve only added a single extra line on line 4. Here we specify that we want to install the privatestuff package from my.pypicom/pypi. In addition to baking the credentials into our image we’re exposing them for everyone who has access to the Dockerfile. Imagine pushing this project (which includes this Dockerfile) to github; everyone will be able to see your credentials!


Also the bad way — build args

Even though this seems like a good option it’s also super bad. Check out the augmented Dockerfile:

As you see we expect three arguments which we’ll provide with the command below:

This will pass the arguments to our Dockerfile but it is very easy to extract them from the layers in the image. We can even see the values when we inspect the docker image’s history:

The command above searches in the docker history of our image (called “my_app”). We extract all lines of text that contain the word PYPI_PASS with grep. This will show us our credentials in plain text!

Docker history will reveal yourbuild-args (image by author)

Slightly less bad but still bad — using and deleting a file

You might come up with the idea to copy over and then delete your a file with credentials. In the example below we copy over .netrc; pip will use the credentials from this file. Then we remove the file again.

https://gist.github.com/mike-huls/79633dd8ef466d040f8c7c97b1d1cd22

In Docker, deleting a file does not actually remove it from the image. Since Docker cashes its layers; all previous layers are still present in the image. This means that a malicious user can easily extract your .netrc file.

Photo by Tim Mossholder on Unsplash

The correct way — docker secrets

The way we’ll got through here is not only the only secure one, it is also very easy to implement! Since I’m a big fan of .env files we’ll use one for this example. Using secrets has three simple steps:

1. Create an .env file

Simple enough. We create a file called .env and give it the following content:PYPI_URL=my.pypi.com/pypi
PYPI_USER=m.huls
PYPI_PASS=supersecretpassword

More info in using environment files here. More info about using env variable files with Docker here.


2. Define our docker build command

We use this command to build our image. Here we pass the file from step 1 to our Dockerfils as a secret.docker build -t "my_app" --secret id=my_env,src=.env .

You can see the command above as: load the .env file and give it a key called my_env. We can use this key in the next step to access the .env.


3. Modify Dockerfile so that it mounts the secret

Here we take the file that and use we passed in the docker build command:

As you can see we just add one extra line of code to our Dockerfile. We mount a secret specifying the id my_env (which we’ve specified in step 2). Next we source the content of the .env which load its content as variables. This is why we can use the variables in the — extra-index-url as specified in the pip install.

The beauty of this approach is that the content of the .env file is only accessible in the RUN command where it’s referred in. This is why we have to put the mount and the pip install together using the &&. After the RUN command is finished the secret is discarded and no evidence of its existence will be left in the container. Docker history will also not contain any information as you can see below.


Bonus: also a correct way but harder to implement

You can also use a two-stage build; this will first build your image in one stage and then copy only the relevant things to the final one, leaving behind all unrequired information, including credentials etc. Check out this article for more information on multi-stage builds in Docker.


Conclusion

In this article we’ve gone through 3 ways of NOT handling secrets in docker images and one easier and correct, secure way. Hardcoding credentials is plain stupidity, build-args won’t save you and even removing credentials files in your image linger in the layers after deletion.

Docker secrets are a very secure and easy to use confidential information and get rid of it once it’s used. With this article I hope to have made your docker images a bit more secure. If you have suggestions/clarifications please comment so I can make improvements. In the meantime, check out my other articles on all kinds of programming-related topics like these:

Happy coding!

— Mike

P.S: like what I’m doing? Follow me!

Join Medium with my referral link — Mike Huls
As a Medium member, a portion of your membership fee goes to writers you read, and you get full access to every story…