4 minutes
Environment variables in Dockerfile and docker-compose
Overview
Environment variables are a simple and popular option to avoid hard-coding credentials into your code or build files, while eliminating the risk of accidentally pushing sensitive information to Git
. Most importantly, utilizing them in your programs is very easy.
Say, you want to retrieve the username and password of your firewall in a Python program. This is all you have to do:
import os
fw_username = os.getenv('FIREWALL_USER', None)
fw_password = os.getenv('FIREWALL_PASS', None)
Recently, I had to dockerize
an API server that I have developed. This API server needed to authenticate to multiple other services to function. We just observed how it is done inside the code but how do you use them in a Dockerfile
to pass them to the program?
Let’s quickly explain what environment variables are before jumping right into the solution.
Environment Variables
An environment variable is a key-value item that can influence how programs behave on a computer. They keep information that the operating system and other programs need. For example the PATH
environment variable that contains a list of directories where executable files are located.
Execute env
command in your shell to get a list of all defined environment variables.
Defining a new environment variable is possible in different ways but I’ll cover three ways here:
- inline with a command you want to run:
LOG_LEVEL=debug ./my_program
We just defined a temporary environment variable here that only affects the my_program and goes out of scope when the my_program exits.
- Session wide:
export LOG_LEVEL=debug
./my_program
Using the export
command, we defined an environment variable which will be available throughout the life of our terminal session. This way, other processes can also use that environment variable.
- Defining environment variables in files that are run on system startup or login sessions:
An example of these files on Linux would be .bashrc
and .profile
files. Appending your environment variables to them will load them when you spawn or login to a shell (e.g. Bash
)
export LOG_LEVEL=debug >> ~/.bashrc
# or
export LOG_LEVEL=debug >> ~/.profile
Using Environment Variables in Dockerfile and docker-compose
Let’s write a simple Dockerfile
to demonstrate a few things:
Write a Dockerfile
FROM python:3.11.1
ARG FW_USER=${FW_USER:-""}
ARG FW_PASS=${FW_PASS:-""}
ENV FW_USER ${FW_USER}
ENV FW_PASS ${FW_PASS}
RUN apt-get update -y \
&& apt-get install -y --no-install-recommends \
&& gcc
&& g++
RUN apt-get -y clean
WORKDIR /app
COPY ./requirements.txt /app
COPY ./*.py /app
RUN pip3 install --no-cache-dir -r /app/requirements.txt
CMD [ "python3", "main.py", "0.0.0.0", "8080"]
The important lines here are the ones starting with ARG and ENV.
- ARG: Defines a build-time variable that can be passed to the
docker build
command using the--build-arg
option or throughdocker compose
, more on that later. - ENV: Sets environment variables that are available to the container at runtime. In this case, the
FW_USER
andFW_PASS
environment variables are set to the values of theFW_USER
andFW_PASS
build-time variables, respectively.
Now, we can write out our docker-compose
file to define how Docker containers should be run.
Write a docker-compose file
As mentioned earlier docker-compose
files define how Docker containers are run and connected together. As of this writing, the provided example is the new docker-compose
format (notice the lack of version
key on top):
services:
api_server:
image: my_program:1.0
build:
context: .
dockerfile: Dockerfile
environment:
- "FW_USER=${FW_USER}"
- "FW_PASS=${FW_PASS}"
container_name: "my_program"
volumes:
- ./:/app
proxy_server:
image: nginx:latest
container_name: nginx_proxy
ports:
- "80:8080"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
What matters for this post are these line:
environment:
- "FW_USER=${FW_USER}"
- "FW_PASS=${FW_PASS}"
Please note that my example is using the docker compose plugin and differs from the docker-compose
command; although the outcome is the same regardless of the command used.
Here is what happens when you run docker compose build
:
- docker compose looks up the environment variables in the shell it is running on for
FW_USER
andFW_PASS
- They get passed to the
Dockerfile
- Docker image gets built with our desired environment variables
- Environment variables get presented to the program when we start the container
Conclusion
In this post, we discussed what environment variables are and how to use them in our programs and Dockerfile
s.
Although this method is relatively safe and way better than hard-coding credentials in your programs, it is still not the best option out there.
whenever possible, you should consider using identity and secret management solutions such as Hashicorp’s Vault.
environment variables docker dockerfile docker-compose linux devops security credential management
717 Words
2023-03-19 13:42