Django / MySQL / Gunicorn / NGINX with Docker  – A step by step guide

In this post i will try to run the Django app we developed in this post using Gunicorn and serve static files using NGINX.

So what is …?

1. NGINX

NGINX is a high-performance Load balancer, a web server which can also be used as a reverse proxy, load balancer, mail proxy, and HTTP cache. It is free and opensource. Some high-profile companies using NGINX include Autodesk, Atlassian, Intuit, T-Mobile, GitLab, DuckDuckGo, Microsoft, IBM, Google, Adobe, Salesforce, VMWare, Xerox, LinkedIn, Cisco, Facebook, Target, Citrix Systems, Twitter, Apple, Intel, and many more. NGINX is made from ground up to offer low memory usage and high concurrency. Rather than starting new processes for each web request, NGINX uses an asynchronous, event-driven approach where requests are handled in a single thread. you can read more about NGINX here.

Gunicorn

It is a Python WSGI HTTP Server for UNIX, ported from Ruby’s Unicorn project. It is simple, light on server resources and fairly fast. It natively supports WSGI, web2py, Django and Paster. Please see the official Gunicorn docs for more information.

Enough with the definitions, let us start.

Modify your requirements.txt as below.

Django==2.1.5
mysqlclient==1.3.14
gunicorn==19.9.0

then, in your docker-compose file replace the command

python3 manage.py runserver 0.0.0.0:8000

with

gunicorn --timeout=30 --workers=2 --bind 0.0.0.0:8000 django_app.wsgi:application

What this means is that spawn 2 workers, which will timeout after 30 seconds of inactivity. Bind to the socket 8000. The general rule of thumb regarding the number of workers is 2 x number of cores.

You need to rebuild the docker image using the command

 docker-compose build

and now run

docker-compose up

and navigate to “http://localhost:3000/polls/“. you should see the pools index page.

But wait for a second, You will see that none of the static files are loading. The reason is that Gunicorn is an application server and it does not serve static files. In order to resolve this issue, we will need NGINX and use it as a reverse proxy for Gunicorn. Nginx also improves performance, reliability, security, and scale. Simply, HTTP requests will be handled by Gunicorn and static ones by Nginx.

So, let’s add NGINX to our stack. For this, we will need to modify the docker-compose to include NGINX like below.

version: "3"
services:
  app:
    restart: always
    build: .
    command: bash -c "python3 manage.py collectstatic --no-input && python3 manage.py migrate && gunicorn --timeout=30 --workers=2 --bind :8000 django_app.wsgi:application"
    volumes:
      - .:/code
      - static-volume:/code/static
    expose:
      - "8000"
    depends_on:
      - db
    networks:
      - web_network
      - db_network
  db:
    image: mysql:latest
    command: mysqld --default-authentication-plugin=mysql_native_password
    volumes:
      - "./mysql:/var/lib/mysql"
    ports:
      - "3306:3306"
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=secret123
      - MYSQL_DATABASE=django_app
      - MYSQL_USER=django_app
      - MYSQL_PASSWORD=django_app123
    networks:
      - db_network
  nginx:
    image: nginx:latest
    ports:
      - "3000:8000"
    volumes:
      - static-volume:/code/static
      - ./config/nginx:/etc/nginx/conf.d
    depends_on:
      - app
    networks:
      - web_network

networks:
  web_network:
    driver: bridge
  db_network:
    driver: bridge

volumes:
  static-volume:

It simply means there are three services for this project: nginx, app, db.
nginx- contains the latest image from docker hub, which is available at port 3000

Now we need an NGINX config file. Create a folder “config/nginx” and add a file “djangoapp.conf”

upstream app {
  ip_hash;
  server app:8000;
}

server {
  location /static/ {
      autoindex on;
      alias /code/static/;
  }

  location / {
        proxy_pass http://app/;
    }
  listen 8000;
  server_name localhost;
}

make sure you modify the “STATIC_ROOT ” in Django settings.py

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'

We will then simply rebuild and run Docker Compose to start our djangoapp

docker-compose build
docker-compose up