How to pass API keys to Docker containers securely
This post is about how to pass API keys to Docker containers securely. The method I describe here can be handy for people who use Docker for small, personal projects. These type of projects usually not using Docker Swarm, hence, they do not have a built-in way to manage secrets.
I am using Linux pass password manager to store my API keys directly on my server. Because I do not store .env file with API keys on my server, I use special script, which generates ephemeral (temporary) .env file every time I start, restart or bring down the Librechat containers.
Before we begin, let’s recall the precedence of environment variables in Docker Compose.
Environment variables precedence in Docker Compose
Source: https://docs.docker.com/compose/how-tos/environment-variables/envvars-precedence/
The order of precedence (highest to lowest) is as follows:
- Set using
docker compose run -ein the CLI. - Set with either the
environmentorenv_fileattribute but with the value interpolated from yourshellor an environment file. (either your default.envfile, or with the--env-fileargument in the CLI). - Set using just the
environmentattribute in the Compose file. - Use of the
env_fileattribute in the Compose file. - Set in a container image in the
ENVdirective. Having anyARGorENVsetting in aDockerfileevaluates only if there is no Docker Compose entry forenvironment,env_fileorrun --env.
Storing API keys in pass
Luckily to us, Linux has a standard password manager called pass. It can be used to store your API keys securely.
To learn how to use it, please refer to the official
passdocumentation.
Assuming, you have already installed and configured pass on your server. Then to store your API keys, you can use the following command:
1pass insert api_keys/service_1/api_key
2pass insert api_keys/service_2/api_key
Case 1: environment attribute in the Docker Compose file
If you use environment attribute in the Docker Compose file, like this:
1# docker-compose.yml
2version: '3.8'
3services:
4 service_1:
5 image: service_1
6 environment:
7 API_KEY: ${API_KEY}
8 ...
9 service_2:
10 image: service_2
11 environment:
12 API_KEY: ${API_KEY}
13 ...Option 1: exporting API keys to environment variables
Then you can export the API keys from pass to the environment variables in your shell like this:
1export API_KEY=$(pass show api_keys/service_1/api_key)
2export API_KEY_2=$(pass show api_keys/service_2/api_key)Then you can start the containers with the following command:
1docker-compose up -dBut it is better to use a wrapper script, which will export the API keys from pass and start the containers for you. You can create a script like this:
1#!/bin/bash
2
3# Export API keys from pass
4export API_KEY=$(pass show api_keys/service_1/api_key)
5export API_KEY_2=$(pass show api_keys/service_2/api_key)
6# Start the containers
7docker-compose up -dAssuming, the script file is called start_containers.sh, you shall make it executable with the following command:
1chmod +x start_containers.shThen you can run the script with the following command:
1./start_containers.sh
Option 2: using temporary created .env file
If you do not want to export the API keys to the environment variables, you can create a temporary .env file with the API keys, launch the containers and remove the file after the containers are started. According to the precedence of environment variables in Docker Compose, the API keys from the .env file will override the ones from the Docker Compose file.
For this you shall create a runner script like this:
1#!/bin/bash
2
3# Create a temporary .env file with API keys
4touch .env
5echo "API_KEY=$(pass show api_keys/service_1/api_key)" >> .env
6echo "API_KEY_2=$(pass show api_keys/service_2/api_key)" >> .env
7
8# Change the permissions of the .env file for security reasons
9chmod 600 .env
10
11# Start the containers
12echo "Starting containers..."
13docker-compose up -d
14
15# Remove the .env file
16echo "Removing .env file..."
17rm -f .env
18
19echo "Done!"
Case 2: Generate .env file from a template
Some applications, like Librechat, require an .env file with a lot of parameter variables to be set, and API keys are just part of it. They are scattered all over the file, and you have to set them manually.
In this case, you can create a template (e.g. .env_template) with all the variables, except the API keys. Instead of real API key values, use special placeholder values. And then you can create a script, which will copy the template file to .env file and fill in the API keys from pass.
Let’s start from the template file. I use the following format for the API key placeholders:
1# .env_template
2API_KEY_1=<API_KEY_1>
3API_KEY_2=<API_KEY_2>
4
5# Other variablesThe actual .env_template for my Librechat installation you can find here.
And then I created this wrapper script brings up and down the containers. I need to cover both up and down cases with the generated .env file, because Librechat’s deploy-compose.yaml has env_file attribute and explicitly references the .env file. In this case the presence of the .env file is required for bringing containers up and also for shutting them down.
Now let’s create the script, which is called docker_wrapper.sh:
1#!/bin/bash
2
3create_env_file(){
4 if [ -e ".env_template" ]; then
5 echo "Preparing .env file"
6 cp ".env_template" ".env"
7 chmod 600 ".env"
8
9 # Here we read the API keys from pass and assign them to variables
10 API_KEY_1=$(pass api_keys/service_1/api_key)
11 API_KEY_2=$(pass api_keys/service_2/api_key)
12
13 # with sed utility we replace the placeholder values with the actual API keys
14 sed -i "s|<API_KEY_1>|${API_KEY_1}|g" ".env"
15 sed -i "s|<API_KEY_2>|${API_KEY_2}|g" ".env"
16
17 # Optionally, you can remove the API keys from the environment variables after using them
18 unset API_KEY_1
19 unset API_KEY_2
20
21 echo ".env file created"
22 else
23 echo "No .env_template file exists. Exit..."
24 exit 1
25 fi
26}
27
28case "${1}" in
29 "-up")
30 echo "Starting up services..."
31 create_env_file
32 docker-compose -f deploy-compose.yml up -d
33 ;;
34 "-down")
35 echo "Shutting down services..."
36 create_env_file
37 docker-compose -f deploy-compose.yml down
38 ;;
39 *)
40 echo "Help"
41 echo " $(basename $0) <option>"
42 echo "Options:"
43 echo " -up : Start Librechat services"
44 echo " -down : Shut down Librechat services"
45 ;;
46esac
47
48if [ -f ".env" ]; then
49 echo "Removing .env file"
50 rm -rf ".env"
51fiNow just make the script executable:
1chmod +x docker_wrapper.shAnd you can run it like this:
1# To bring up the containers
2./docker_wrapper.sh -up
3
4# To bring down the containers
5./docker_wrapper.sh -downEnjoy!
Now you learned how to pass API keys to Docker containers securely with the help of pass password manager.