Using environment variables to configure Django and other Python applications is awesome, but using them with Apache and mod_wsgi in Docker is a tricky process to get right.
That's why I created a step-by-step tutorial and a sample application to review all the info you need in one place.
While this tutorial uses Docker and Django, the same steps apply whether you're using a virtual machine or a different Python framework.
Review the code directly from the repository >
When hosting a Python WSGI compatible framework like Django in Apache with mod_wsgi, the only environment variables populated in the os.environ dictionary are those that exist in the environment of the script that starts Apache. But instead of having to mess with Apache's service manager settings (e.g. systemd or systemctl), there's a better way.
Most Apache distributions provide a shell script specifically for the purpose of setting environment variables that are made available to modules such as mod_wsgi.
It's then a matter of knowing the location of this shell script since it can be different depending on the Linux distribution. For example:
We'll be using Debian-based python:3.9-slim-buster Docker image.
Our goal is for secrets to be fetched as key/value pairs and written to the envvars file in the typical shell environment variables format:
1export FIRST_NAME="The"
2export LAST_NAME="Mandalorian"
But from where and how do we fetch the app config and secrets to populate that file?
Since we're fans of Doppler :) let's start with a Doppler CLI example. But it's important to note that the mechanics of "fetch secrets, then append to file" can easily be adapted.
First, you need to set up your project in Doppler. To make the process easier, Import to Doppler to follow along.
Then use the Doppler CLI inside the Docker container to fetch the secrets:
Note: This requires a DOPPLER_TOKEN environment variable with a Service Token value
1# Transform JSON key:value pairs into export statements using jq
2if [ -n "$DOPPLER_TOKEN" ]; then
3 echo '[info]: Appending environment variables to /etc/apache/envvars using Doppler CLI'
4 doppler secrets download --no-file | jq -r $'. | to_entries[] | "export \(.key)=\'\(.value)\'"' >> /etc/apache2/envvars
5fi
Did you notice that I used single quotes, not double quotes around the values?
That's because it gives you the flexibility of storing secrets with double quotes such as JSON in Doppler. As an example, you could use this to dynamically set Django's ALLOWED_HOSTS for any environment.
1ALLOWED_HOSTS = json.loads(os.environ['ALLOWED_HOSTS'])
While you could also use a .env file, I wouldn't recommend it. Instead, consider using a secrets manager— why not Doppler?
Knowing the downsides to using .env files, If you must use them in your environment, the process is still straightforward:
1if [ -f "$PWD/.env" ]; then
2 echo '[info]: Appending environment variables to /etc/apache/envvars from .env file'
3 cat "$PWD/.env" >> /etc/apache2/envvars
4fi
Now that we know how to pass environment variables from Apache to mod_wsgi, let's move onto getting this working in Docker.
Let's review the task of configuring a Python Django Application using Apache and mod_wsgi in Docker into three parts:
If you only want to review the working code examples, review the examples on GitHub >
Since this isn't a Docker or Apache tutorial, we won't go into depth about the Dockerfile or Apache site config file. Do you have questions, need help, or want to share your thoughts? Check out our community forum >
Running your application in Docker is usually a case of setting the CMD, for example:
1CMD ["python", "src/app.py"]
But it's trickier here because we need to append the environment variables to /etc/apache2/envvars before running Apache.
Since this requires multiple commands, let's create a custom script:
1#!/bin/bash#
2
3apache-doppler-start
4
5set -e
6
7echo 'ServerName localhost' >> /etc/apache2/apache2.conf # Silence FQDN warning
8
9# Doppler CLI
10if [ -n "$DOPPLER_TOKEN" ]; then
11 echo '[info]: Appending environment variables to /etc/apache/envvars from Doppler CLI'
12 doppler secrets download --no-file | jq -r $'. | to_entries[] | "export \(.key)=\'\(.value)\'"' >> /etc/apache2/envvars
13fi
14
15# Mounted .env file
16if [ -f "$PWD/.env" ]; then
17 echo '[info]: Appending environment variables to /etc/apache/envvars from .env file'
18 cat "$PWD/.env" >> /etc/apache2/envvars
19fi
20
21# Run Apache
22apache2ctl -D FOREGROUND
Here's an example Apache site config file for a Django application:
1# wsgi.conf<VirtualHost *:80>
2 ServerName django-apache-mod-wsgi
3 ServerAlias django-apache-mod-wsgi
4 ServerAdmin webmaster@doppler
5
6 # Defining `WSGIDaemonProcess` and `WSGIProcessGroup` triggers daemon mode
7 WSGIDaemonProcess django-apache-mod-wsgi processes=2 threads=15 display-name=%{GROUP} python-
8path=/usr/local/lib/python3.9/site-packages:/usr/src/app
9 WSGIProcessGroup django-apache-mod-wsgi
10 WSGIScriptAlias / /usr/src/app/doppler/wsgi.py
11
12
13 <Directory /usr/src/app/doppler/><Files wsgi.py>
14 Require all granted
15 </Files></Directory>
16
17 # Redirect all logging to stdout for Docker
18 LogLevel INFO
19 ErrorLog /dev/stdout
20 TransferLog /dev/stdout
21</VirtualHost>
The Docker file is reasonably straightforward, installing the Doppler CLI and Apache dependencies before copying the Django source code, custom script, and Apache site config:
1FROM python:3.9-slim-buster
2
3ENV PYTHONUNBUFFERED 1
4ENV PYTHONDONTWRITEBYTECODE 1
5
6# Install Doppler CLI and related dependencies
7RUN apt-get -qq update && apt-get install -y apt-transport-https ca-certificates curl gnupg jq && \
8curl -sLf --retry 3 --tlsv1.2 --proto "=https" 'https://packages.doppler.com/public/cli/gpg.DE2A7741A397C129.key' | apt-key add - &&
9echo "deb https://packages.doppler.com/public/cli/deb/debian any-version main" | tee /etc/apt/sources.list.d/doppler-cli.list && \
10apt-get -qq update && apt-get install doppler
11
12# Install Apache and related dependencies
13RUN apt-get install --yes apache2 apache2-dev libapache2-mod-wsgi-py3 && \
14 apt-get clean && \
15 apt-get remove --purge --auto-remove -y && \
16 rm -rf /var/lib/apt/lists/*
17
18WORKDIR /usr/src/app
19
20COPY requirements*.txt .
21RUN pip install --quiet --no-cache-dir --upgrade pip && \
22 pip install --quiet --no-cache-dir -r requirements.txt
23
24# Application source
25COPY src/ ./
26
27# Custom CMD script
28COPY apache-doppler-start /usr/local/bin/
29
30# Apache site config
31COPY wsgi.conf /etc/apache2/sites-enabled/000-default.conf
32
33EXPOSE 80 443
34
35# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
36STOPSIGNAL SIGWINCH
37
38CMD ["apache-doppler-start"]
With all the pieces in place, we can now build the Docker image (clone the sample repository to follow along):
1docker image build -t django-apache-mod-wsgi:latest .
Now we're ready to run the container!
Let's start with a Doppler example, then with an .env file.
With Doppler, you'll first need to set a DOPPLER_TOKEN environment variable to the value of a Service Token. This is what provides read-only access to a specific Doppler config in production environments.
Usually, this would be securely set by your deployment environment (e.g. GitHub Actions Secret) but for this exercise, we'll set it manually:
1# Service token value created from Doppler dashboard
2export DOPPLER_TOKEN="dp.st.xxxx"
Now run the container:
1docker container run \
2 -it \
3 --init \
4 --name doppler-apache-mod-wsgi \
5 --rm \
6 -p 8080:80 \
7 -e DOPPLER_TOKEN="$DOPPLER_TOKEN" \
8 django-apache-mod-wsgi
To run the .env file version, we'll use the sample.env file from the sample repository:
1# sample.env
2export DJANGO_SETTINGS_MODULE='doppler.settings'
3export DEBUG='yes'export ALLOWED_HOSTS='["*"]'
4export SECRET_KEY='bf5e1b31-6ba7-48e2-9175-f2293671e6df'
Then to run the container:
1docker container run \
2 -it \
3 --init \
4 --name dotenv-apache-mod-wsgi \
5 --rm \
6 -v $(pwd)/sample.env:/usr/src/app/.env \
7 -p 8080:80 \
8 django-apache-mod-wsgi
Now you know how to configure Python applications hosted with Apache and mod_wsgi running in Docker using environment variables for app configuration and secrets.
After using the Doppler CLI versus managing .env files through our exercise, the difference is clear.
Manage environment variables, rotate secrets, and ship securely with Doppler >
Stay up to date with new platform releases and get to know the team of experts behind them.
Trusted by the world’s best DevOps and security teams. Doppler is the secrets manager developers love.