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:

  1. Set using docker compose run -e in the CLI.
  2. Set with either the environment or env_file attribute but with the value interpolated from your shell or an environment file. (either your default .env file, or with the --env-file argument in the CLI).
  3. Set using just the environment attribute in the Compose file.
  4. Use of the env_file attribute in the Compose file.
  5. Set in a container image in the ENV directive. Having any ARG or ENV setting in a Dockerfile evaluates only if there is no Docker Compose entry for environment, env_file or run --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 pass documentation.

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 -d

But 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 -d

Assuming, the script file is called start_containers.sh, you shall make it executable with the following command:

1chmod +x start_containers.sh

Then 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 variables

The 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"
51fi

Now just make the script executable:

1chmod +x docker_wrapper.sh

And 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 -down

Enjoy!

Now you learned how to pass API keys to Docker containers securely with the help of pass password manager.