A complete guide to using environment variables and files with Docker and Compose

Keep your containers secure and flexible with this easy tutorial

A complete guide to using environment variables and files with Docker and Compose
This is a pretty beautiful environment that just needs some variables! (image by Ales krivec on Unsplash)

The goal of this article is show you the benefits of working with environment variables in Docker and to demonstrate how to do so. Environment variables keep your app secure, flexible and organized. The main advantages:

  • keep confidential information confidential: prevent baking your passwords and secrets into your image
  • Organize confidential information in one file; we can .gitignore all of it
  • Reuse env files by injecting them in multiple containers; all configurations in one place

In this article we’ll have hands-on examples on how to pass environment variables to containers and docker-compose.yml files. Also we’ll check out how to organize env vars in a file that we can pass, again, to both our containers and configs. Let’s code!


Before we begin..

Obviously, we are going to use Docker and Docker-Compose for this. It’s recommended to first read this and this article if you’re unfamiliar with either.

Why and how custom exceptions lead to cleaner, better code
Clean up your code by creating your own custom exceptions

Setting up

We’ll create a simple Python script called environprinter.py that prints our all keys and values in our environment:

We’ll bake this file in a simple docker image with the Dockerfile below.

Execute docker build-t environ_image . to build the image. As seen on line 4 of the Dockerfile; our python script will execute the moment we spin up our container. If you execute docker run environ_image you’ll see some variables like HOSTNAME, LANG, PYTHON_VERSION etc. being printed. You’ll see that there are always some environment variables that concern your path, hostname etc. (more about in this article).

Destroying Duck Hunt with OpenCV — image analysis for beginners
Write code that will beat every Duck Hunt high score

Passing env vars to the container

In this section we’ll check out how to load variables into the environment of the container so that the Python script that’s running in this container has access to it. Understand that this differs from passing env vars to the run-configuration; we’ll check this out later.

1. Pass env vars to a container with Docker run

So we have an image that executes a Python script on startup. Our goal is now to start the container, pass an env var to the container and then run the Python script (that will print out all of our env vars).

We can achieve this with a single command:
just execute docker run -e PASSWORD=SECRET environ_image. Notice that PASSWORD and SECRET now show up in the output!

Pass multiple env vars with te following:
docker run -e USERNAME=mike -e PASSWORD=bestpassword environ_image.

SQLAlchemy for absolute beginners
Creating a database engine and execute SQL from Python

2. Pass env files to a container with Docker run

Typing out all of the env vars can become a bit time-consuming. Let’s create a file called .env and put all of our env vars in there. This is what the contents look like:

USERNAME=mike 
PASSWORD=bestpassword

We can pass all of the variables contained in the file in one go by executing: docker run --env-file=.env environ_image.

You can even pass multiple .env files like this:
docker run --env-file=.env --env-file=.env2 environ_image.
Just remember that the last env file overwrites duplicate keys. So if you have a USER=1 in .env and USER=2 in .env2 then only one env variable will exist in the container’s environment by the key USER and it will have the value 2.

Cython for absolute beginners: 30x faster code in two simple steps
Easy Python code compilation for blazingly fast applications

3. Pass env vars to a container with docker-compose

Call me lazy but I don’t like to type out the docker run commands we’ve used earlier. With docker-compose we can create a config file that we can execute to do the same thing. Just create a file called docker-compose.yml with the following content:

version: '3.8' 
 
services: 
 
  env_printer: 
    image: "environ_image"

Now we can call docker-compose up and it will do the exact same thing. We can add individual env vars by adding the last 3 lines below:

version: '3.8' 
 
services: 
 
  env_printer: 
    image: "environ_image" 
    environment: 
      - USER=mike 
      - PASS=123
NOTICE: do NOT include confidential information (like the credentials in the example above) into your compose file. This information can end up in your (public) repository. Read the article below for more information on using .env file for security.
Keep your code secure by using environment variables and env files
Securely load a file containing all of our app’s required, confidential data like passwords, tokens, etc

4. Pass env files to a container with docker-compose

Notice that in the previous example we’ve hardcoded our password into our docker-compose.yml. This is definitely not a good idea since the compose-file can end up in your Github repository.

Therefore it’s a better (more secure) idea to add all environment variables in an .env file. You .gitignore the file so it doesn’t end up on Github. Passing the .env file into your container is just a matter of adding a few lines:

version: '3.8' 
 
services: 
 
  env_printer: 
    image: "environ_image" 
    environment: 
      - USER=mike 
    env_file: 
      - .env 
      - .env2

Secure Your Docker Images with Docker Secrets
Add docker secrets to prevent your docker image from leaking passwords

Passing env vars to the container config

This section details how to pass env vars to your docker-compose.yml file. The main use-cases:

  1. You don’t want to pass the entire .env file into the container, just one or more keys
  2. You want to set other docker attributes (like label) with values from the .env file

1. Setup

In order to demonstrate we’ll first need a .env file. Create a file named .env with the following content:

ENVIRONMENT=acceptation 
USERNAME=mike 
PASSWORD=test 
API_KEY=SOME_KEY

Now our goal is to pass only our API key into our container and use the value for the ENVIRONMENT key as a label in Docker. This is achieved using the following docker-compose.yml:

version: '3.8' 
 
services: 
 
  env_printer: 
    image: "environ_image" 
    labels: 
      environment: ${ENVIRONMENT} 
    environment: 
      - API_KEY=${API_KEY}
Applying Python multiprocessing in 2 lines of code
When and how to use multiple cores to execute many times faster

2. Check the config

Before we spin up our container, let’s check out what the configuration will be: docker-compose --env-file=some_folder/.env config. This command will print out your compose-file after Docker fills it in. This is the result:

services: 
  env_printer: 
    environment: 
      API_KEY: SOME_KEY 
    image: environ_image 
    labels: 
      environment: acceptation 
version: '3.8'

As you see the label is set to acceptation and we’re passing the API_KEY value from our .env but no other variables.


3. Run the container

When we execute docker-compose --env-file=some_folder/.env up we start the container and see that our Python program correctly prints out the value associated with the API_KEY variable!


Why Python is so slow and how to speed it up
Take a look under the hood to see where Python’s bottlenecks lie

Conclusion

I hope to have shed some light on how to make Docker more secure and convenient to use. Environment variables help you keep all your confidential information in one place, you avoid baking it into your image and it makes running containers even more dynamic.

If you have suggestions/clarifications please comment so I can improve this article. 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…