Tutorials
5 min read

How to Set Environment Variables for a Python Django Application using Apache and mod_wsgi in Docker

Learn the how to use environment variables with Apache and mod_wsgi for Python applications in Docker

Aug 15, 2021
Ryan Blunden Avatar
Ryan Blunden
Senior Developer Advocate
How to Set Environment Variables for a Python Django Application using Apache and mod_wsgi in Docker
Back to the blog
How to Set Environment Variables for a Python Django Application using Apache and mod_wsgi in Docker
Share
Tutorials

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 >

Environment Variables, Apache, and mod_wsgi

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:

  • Debian/Ubuntu: /etc/apache2/envvars
  • CentOS: /etc/sysconfig/httpd

We'll be using Debian-based python:3.9-slim-buster Docker image.

Appending App Config and Secrets to the Environment Variables File

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.

Set up your project

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.

Docker Configuration for Apache and mod_wsgi

Let's review the task of configuring a Python Django Application using Apache and mod_wsgi in Docker into three parts:

  1. Custom Start Script
  2. Apache Site Config
  3. Dockerfile

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 >

1. Custom Start Script

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

2. Apache Site Config

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>

3. Docker file

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!

Running the Django Application with Apache and mod_wsgi in Docker

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

.env File

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

Summary

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.

Related Content

Explore More