Skip to main content

Django in Production with mod_wsgi and Docker

This article will show you how to run Django in production with docker and apache2/mod_wsgi. While there are several guides on how to do it, I found no simple enough tutorial on how to do it since all of the existing solutions require a lot of configuration or a custom docker image. The solution I found is way quicker and requires close to no configuration.

Guides I found:

  • Django with Apache and mod_wsgi assumes you have installed everything already and requires configuration possibly unnecessary for a dockerized version
  • mod_wsgi explains how to install mod_wsgi, but not how to use Django with it

So, let's do this. If you just want to see the final Dockerfile, scroll to the end of the article :)

Prerequisites

I assume you have some Dockerfile with Django application setup and run Django via the command that spins up the development server:

FROM python:3.6

WORKDIR /code/

# copy and install requirements first to leverage caching
COPY requirements.txt /code/
RUN pip install -r requirements.txt

# copy the actual code
COPY . /code/

CMD ./manage.py runserver 0.0.0.0:8000

Step 1: Install Apache

We basically need a working Apache setup to run python code via mod_wsgi. So in our Dockerfile, we need to install apache2 and apache2-dev.

RUN apt-get install apache2 apache2-dev

Step 2: install mod_wsgi

We want to run our project within a container. The mod_wsgi documentation says the easiest and preferred using docker is to use mod_wsgi-express as it does not require any configuration. mod_wsgi-express can be used as a command after you have installed it via pip. So we need to add:

RUN pip install mod_wsgi

Step 3: run Django inside Apache with mod_wsgi

Lastly, we simply have to run mod_wsgi-express which will start an apache instance with our Django project. This can be achieved via:

mod_wsgi-express start-server /code/project_name/wsgi.py --user www-data --group www-data

The wsgi.py file is auto-generated by Django's startproject command and inside you app's folder, i.e. django-project/project_name. So, to run our project within Apache after we've set-up everything in the Dockerfile, we have to add a CMD command at the end of the Dockerfile:

CMD mod_wsgi-express start-server /code/connect_web/wsgi.py --user www-data --group www-data

The --user and --group parameters make sure Apache isn't run as root which results in errors. If you want to find out, just run the command without them.

Final Dockerfile: Installing and running Apache and mod_wsgi within Docker

Your final Dockerfile should now look like this:

FROM python:3.6

# update packages
RUN apt-get -qq update
RUN apt-get install --yes apache2 apache2-dev
RUN pip install mod_wsgi

RUN mkdir /code
WORKDIR /code

COPY . /code/

CMD mod_wsgi-express start-server /code/project_name/wsgi.py --user www-data --group www-data

Troubleshooting

If you have any problems, check the logs. Their locations are outputted when starting the container:

web_1     | Server URL         : http://localhost:8000/
web_1     | Server Root        : /tmp/mod_wsgi-localhost:8000:0
web_1     | Server Conf        : /tmp/mod_wsgi-localhost:8000:0/httpd.conf
web_1     | Error Log File     : /tmp/mod_wsgi-localhost:8000:0/error_log (warn)
web_1     | Request Capacity   : 5 (1 process * 5 threads)
web_1     | Request Timeout    : 60 (seconds)
web_1     | Startup Timeout    : 15 (seconds)
web_1     | Queue Backlog      : 100 (connections)
web_1     | Queue Timeout      : 45 (seconds)
web_1     | Server Capacity    : 20 (event/worker), 20 (prefork)
web_1     | Server Backlog     : 500 (connections)
web_1     | Locale Setting     : en_US.UTF-8

If you want to monitor them, just tail -f them:

tail -f /tmp/mod_wsgi-localhost:8000:0/error_log

Permission problems

At first, I got an Internal Server Error when opening the page on my machine. After checking the logs as described above, mod_wsgi seemed to have problems with file permissions on .logs/debug.log. A simple chown www-data /code/.logs/debug.log from within the container which makes www-data the owner of the file solved it for me.