User Tools

Site Tools


2017:09:14:adding-django-to-nginx-with-docker

Adding Django to Nginx with Docker

In order to reflect the latest updates to the software mentioned in this article, this entry has been revised. Further information may be found in the Changelog.

Introduction

In prior articles, I showed you how to Secure Nginx in Docker, and then I followed up with Tying MQTT, WebSockets, and Nginx together with Docker. Now that I've covered underlying communications for secure web-serving, along with real-time messaging with MQTT and WebSockets, it's time to focus on setting up the foundation to build a project with the Python programming language and the Django web framework to serve content to users, while also focusing on speed with Gunicorn.

In subsequent articles, I'm planning on covering:

  • Building a browser-based, real-time GUI.
  • Building a REST API.
  • Load-balancing and failover.

Prerequisites

Caveats

  • Please read Tying MQTT, WebSockets, and Nginx together with Docker. Real-time messaging is optional for this article, but we're going to use that article's foundation in Docker Compose for adding micro-services.
  • WSGI stands for Web Server Gateway Interface and is a communications standard for Python-based applications and web servers.
    • Perhaps the two most popular choices for interfacing Django to Nginx via the WSGI are:
      1. Gunicorn (Green Unicorn)
    • There's endless discussion about why you need WSGI, which is better1) 2), and benchmarks.
    • All of that said, I use Gunicorn, and will cover that here; but it is interchangeable, so you can switch it out to whatever works for you.
  • As you read this article and copypasta, please be sure to make appropriate substitutions:
    • %YOURPROJECTNAME% with an alphanumeric project name.
    • %HOSTNAME% with your FQDN.

Setup source

mkdir -pv \
    /root/src/docker-django-gunicorn-base/ \
    /root/srv/ \
    ;

Django Docker bundle

Since I'm using more than just a Dockerfile for assembling this Django image, I've created a bundle. You'll want to download and extract it to /root/src/docker-django-gunicorn-base/ from my repository on GitHub:

mkdir -pv /root/src/docker-django-gunicorn-base/;
wget -qO- \
    https://github.com/LTGIV/docker-django-gunicorn-base/archive/master.tar.gz | \
    tar xzv \
    --strip-components=1 \
    -C /root/src/docker-django-gunicorn-base/ \
    ;

Configure Packages

  • /root/src/docker-django-gunicorn-base/packages.txt is a list of Ubuntu packages which will be installed when the Docker image is built.
  • Python 3 is installed by default, but you may modify this file to change it to Python 2.
    • If you need to use Python 2, but want to write Python 3 compatible code, consider incorporating the six module into your project, following the Configure Modules section.
  • Append this file with packages that suit your needs.

Configure Modules

  • /root/src/docker-django-gunicorn-base/requirements.txt is a list of Python modules which will be installed when the Docker image is built.
  • Django and Gunicorn are both installed by default.
  • Append this file with modules that suit your needs.

Docker Compose

In the diagram at the top, I drew an ideal layout that could include session and database connectivity for Django, which I feel deserves a separate article from this one.

If you wanted to set that up now, you would modify this docker-compose.yaml file to include instances and peer connectivity for reciprocal containers to key-value storage and your preferred database server(s).

Download docker-compose.yaml to your /root/srv/ directory:

wget \
    -O /root/srv/docker-compose.yaml \
    https://thad.getterman.org/_export/code/2017/09/14/adding-django-to-nginx-with-docker?codeblock=3 \
    ;
docker-compose.yaml
---

version: '3'

services:

################################################################################

    web:

        container_name: nginx
        image: nginx:latest
        restart: always
 
        # Public
        ports:
            - 80:80      # HTTP
            - 443:443    # HTTPS

        networks:
            - django_web_%YOURPROJECTNAME%

        depends_on:
            - django_%YOURPROJECTNAME%

        volumes:
            - /root/srv/letsencrypt/etc/:/etc/letsencrypt/:ro
            - /root/srv/nginx/etc/:/etc/nginx/:ro
            - /root/srv/nginx/log/:/var/log/nginx/
            - /root/srv/sites/:/sites/:ro
            - /root/srv/sites/default/:/usr/share/nginx/html/:ro
 
################################################################################
 
    django_%YOURPROJECTNAME%:

        container_name: %YOURPROJECTNAME%
        image: django:latest
        restart: always

        build:
            context: /root/src/docker-django-gunicorn-base/
 
        # Peer containers
        expose:
            - 8000/tcp                # Gunicorn

        networks:
            - django_web_%YOURPROJECTNAME%

        volumes:
            - /root/srv/django/projects/%YOURPROJECTNAME%/:/django/
            - /root/srv/sites/%HOSTNAME%/public/:/static/
 
################################################################################

networks:

    django_web_%YOURPROJECTNAME%:
        driver: bridge
 
################################################################################

Build

docker-compose \
    --file /root/srv/docker-compose.yaml \
    build \
    --no-cache \
    django_%YOURPROJECTNAME% \
    ;

Test

Once the build of the Django Docker image finishes, you should be able to run the following commands to view the software versions in your new Docker image:

for softwareVersion in \
    'django-admin' \
    'pip' \
    'python' \
    ; do
    echo -e "[ ${softwareVersion} ]\n$(docker-compose --file /root/srv/docker-compose.yaml run --rm django_%YOURPROJECTNAME% ${softwareVersion} --version)\n"
done

You should see similar to:

[ django-admin ]
1.11.5

[ pip ]
pip 9.0.1 from /home/django/.local/lib/python3.5/site-packages (python 3.5)

[ python ]
Python 3.5.2

Initialize

docker-compose \
    --file /root/srv/docker-compose.yaml \
    run \
    --rm \
    -p 127.0.0.1:8000:8000 \
    django_%YOURPROJECTNAME% \
    ;

The first time that you run this command, you will be asked to enter the name of your project.

Subsequent runs will launch your project via Gunicorn, and allow you to preview your project at http://localhost:8000/.
I recommend that you use the Debug option, so that as you write Python code, changes to your project are instantly refreshed.

To stop Gunicorn, press CTRL-C (^C).

Configure Nginx

Since I'll setup Django for running an entire domain, I'll connect Django to the root of the domain ( %HOSTNAME%/ ), while configuring Nginx to serve our static assets ( %HOSTNAME%/static/ ). The excerpt to accomplish this is:

location / {

    proxy_pass         http://django_%YOURPROJECTNAME%:8000;
    proxy_redirect     off;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Host $server_name;

}

location ^~ /static {
    alias /sites/%HOSTNAME%/public;
}

Using the Site template as an example, the bottom of the configuration file should now be:

template.conf
# https://%HOSTNAME%/
server {
 
    location / {
 
        proxy_pass         http://django_%YOURPROJECTNAME%:8000;
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
 
    }
 
    location ^~ /static {
        alias /sites/%HOSTNAME%/public;
    }
 
...

Micro-services

Debug

Django carries its own internal web server, but it is not for production use, it is only for local development.

docker-compose \
    --file /root/srv/docker-compose.yaml \
    run \
    --rm \
    -p 127.0.0.1:8000:8000 \
    django_%YOURPROJECTNAME% \
    manage.py runserver 0.0.0.0:8000 \
    ;

Start

docker-compose \
    --file /root/srv/docker-compose.yaml \
    up \
    -d \
    ;

You can monitor the logs in /root/srv/nginx/log/ and /root/srv/django/projects/%YOURPROJECTNAME%/log/, or omit the -d argument above, if you want to watch the services launch.

When you first attempt to access your domain, you will receive an error message until you update ALLOWED_HOSTS in /root/srv/django/projects/%YOURPROJECTNAME%/%YOURPROJECTNAME%/settings.py

Restart

docker-compose \
    --file /root/srv/docker-compose.yaml \
    restart \
    ;

Stop

docker-compose \
    --file /root/srv/docker-compose.yaml \
    down \
    ;

Upgrade

  1. Pull the latest copy of Nginx:
    docker-compose \
        --file /root/srv/docker-compose.yaml \
        pull \
        web \
        ;
  2. Force a new build of Django:
    docker-compose \
        --file /root/srv/docker-compose.yaml \
        build \
        --no-cache \
        django_%YOURPROJECTNAME% \
        ;
  3. Stop (and remove) the containers, followed by Starting the containers again.

    The reason that I don't issue a restart is because the containers would simply stop and start. By using the down command listed above, I stop the containers, and remove them. This assists for the next step, where I remove unused containers and images.

  4. Clean-up:
    docker rm $(docker ps --all --quiet --no-trunc --filter 'status=exited');
    docker rmi $(docker images --quiet --filter 'dangling=true');

Conclusion

Hopefully this article assisted you in setting up a production-ready instance of Django and Nginx with Docker.

I've tried to cover various needs with the Docker image itself, including automatically configuring Django's static files each time that you spin up my Docker image. If you find an error or feel that I left something out, please let me know, so that I can make further updates to the Docker image and/or this article.

One of my favorite Django books is Django Unleashed, and the author, Andrew Pinkham responds to queries about his book, quickly. In prior questions to, and subsequent answers from Mr. Pinkham3) 4), I even managed to get an errata update out of him. It's a great read, whether you consider yourself a novice, intermediate, or advanced Django developer.

References

External links

Changelog

Dates and/or times are in the UTC timezone.

2018-08-01

2017/09/14/adding-django-to-nginx-with-docker.txt · Last modified: 2018/08/01 18:28 by Louis T. Getterman IV