Docker Compose with Nginx, Django, Gunicorn, MySQL and Static File setup

Docker can be a real productivity saviour when you get to know how to use it well. It’s really great with automating deployments and even horizontally scaling your App. As you might have guessed, I actually use Docker in production for Site Monki application and I would like to share my experiences on how to set it up for production workloads.

Site Monki’s web dashboard is built on Django with MySQL db. There are a handful of tutorials online on how to setup Django with Docker, but most miss out on how to serve static files in production. Django requires a web server to serve static files such as CSS, JS, Image for the production setup while it the single-threaded development server can serve everything all with the in-built dev server. So this tutorial really is about configuring Django with Docker to serve static files.

For this purpose I use Nginx. It’s a great, lightweight web server. I use it as a reverse proxy to Docker containers running the Django app as well as a static content web server that our Django app relies on. He’s the flow;

Configuring Nginx Reverse Proxy

So we start with configuring an Nginx instance that will reverse proxy client requests to our docker fleet. We pass in all the http request header data to our Django app.

server {
    server_name mydjangoapp.com www.mydjangoapp.com;

    ....
   # proxy configures 
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header HOST $http_host;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_set_header X-HTTPS-Protocol $ssl_protocol;
    proxy_set_header X-NginX-Proxy true;

    location / {
        proxy_pass http://127.0.0.1:9090;
    }

    ....

  }

Nginx web server container

We then setup Nginx Docker container to serve static files that our Django App running on port 8000 via gunicorn generates. This Docker container will serve static assets from url https://mydjangoapp.com/static to the client but proxies the rest of the queries to the Django app.

Notice we Nginx to serve static files from folder /staticfiles. We shall later map named docker volume staticfiles from our Django container to folder /staticfiles in this Nginx container using docker-compose.yml file.

Dockerfile

FROM nginx:1.15.12-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

nginx.conf file

upstream mydjangoapp {
    server web:8000;
}

server {

    listen 80;

    location / {
        proxy_pass http://mydjangoapp;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /static/ {
        alias /staticfiles/;
    }

}

Django with Gunicorn setup

Now we setup Django to collect all static files of all the apps into a single staticfiles folder in settings.py. We also tell Django that the url for static files is /static

import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_DIR = os.path.join(BASE_DIR, 'static')
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

...

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    STATIC_DIR,
]
...

We have to add gunicorn python library in a requirements.txt file. Here’s our django_mydjangoapp Dockerfile for the Django app;

FROM python:3.6.5-jessie
ENV PYTHONUNBUFFERED 1
ENV SRC_DIR=/django_mydjangoapp
RUN mkdir -p $SRC_DIR
WORKDIR $SRC_DIR
ADD ./requirements.txt $SRC_DIR/requirements.txt
RUN pip install -r requirements.txt
ADD . $SRC_DIR

Docker-compose.yml file

We put it together in a docker-compose.yml file where we specify all our Docker containers, the ports we want to run them on and how they interact with each other.

Notice here that we have two named docker volumes; staticfiles and mysql_data. The staticfiles volume maps to actual location where our Django app puts all static assets. We then use this volume in the nginx container to serve static content. mysql_data is for persisting our app database.

version: '2.1'
services:
  web:
    build:
      context: ./django_mydjangoapp
      dockerfile: Dockerfile
    command: bash -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn --bind 0.0.0.0:8000 -w 4 mydjangoapp.wsgi"
    volumes:
      - staticfiles:/django_mydjangoapp/staticfiles
    ports:
      - 8000
    depends_on:
      db:
        condition: service_healthy
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - 9090:80
    volumes:
      - staticfiles:/staticfiles
    depends_on:
      - web
  db:
    image: mysql:5.7
    ports:
      - 3306
    environment:
      MYSQL_ROOT_PASSWORD: mydjangoapp
      MYSQL_USER: mydjangoapp
      MYSQL_PASSWORD: mydjangoapp
      MYSQL_DATABASE: mydjangoapp
    healthcheck:
      test: ["CMD", "mysqladmin", "-u$MYSQL_USER", "-p$MYSQL_PASSWORD",  "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10
    restart: always
    volumes:
      - mysql_data:/var/lib/mysql
  
volumes:
  mysql_data:
  staticfiles:

That’s pretty much it. Simply install Nginx in the host server and add nginx.conf in /etc/nginx/sites-available/ then run docker-compose up in our app folder.

Leave a Reply

Your email address will not be published. Required fields are marked *