Easy methods to Deploy previous Django mission utilizing NGINX, Docker and UWSGI



Hello everybody! ? I keep an previous web site for a shopper that was initially developed in 2015. It hasn’t been up to date for the final 5 years and makes use of Django 1.7 and a few Django extensions which haven’t been up to date for Django 3. I lately determined to maneuver the web site to a brand new server and needed to repackage the Django code in a Docker container. It took me a while to get it working accurately so I hope this text can prevent a while should you ever end up in the same state of affairs.

Right here’s what you’ll be studying:

  • How does an everyday Django mission construction seem like for UWSGI
  • Easy methods to bundle the Django app in a Docker container together with UWSGI
  • Easy methods to run NGINX on the server and arrange a reverse proxy for the Docker container

Word: I do know there are safety points with the older Django variations. I’m not selling using the previous variations however generally one has to do an emergency migration and updating a library or framework with breaking modifications isn’t possible. This was a type of eventualities.

Why Dockerize the app?

On the previous server, I didn’t have some other Python tasks that I used to be engaged on. This made it simple to maintain an older Python model operating. Nevertheless, on the brand new server, I used to be already engaged on another tasks and I didn’t wish to pollute my international bin folder with an previous Python model. Furthermore, I needed to be sure that if I ever have to maneuver the server once more, I didn’t wish to set up the required Python model myself. This made it fairly apparent that Docker was the best way to go.

One other query I requested myself was why not put NGINX in Docker as properly? This manner my setup would have contained two Docker containers managed through docker-compose. One container would have run NGINX and the opposite one would have run UWSGI.

The reply is I attempted nevertheless it didn’t work out. I had one other app operating on the brand new server in a Docker container and I had already arrange NGINX for that. Proxying requests from an NGINX occasion operating on the host server to an NGINX occasion operating in a container didn’t sound correct initially. However I nonetheless went forward. I don’t need to name myself a hacker at coronary heart if I can’t go towards my very own higher judgment ?

I spent an entire day making an attempt to determine all kinds of points this double NGINX setup was inflicting me. I lastly gave up and determined to solely containerize UWSGI and Django app and proxy requests to UWSGI through the NGINX occasion operating on the host machine.

Previous listing construction

You could know what my listing construction regarded like after I started. This provides you with a clearer concept of how every command works in relation to the structure.

That is what I had earlier than I began porting this mission to Docker:

$ cd mission
$ tree

├── db.sqlite3
├── project_app
│   ├── admin.py
│   ├── __init__.py
│   ├── fashions.py
│   ├── sitemap.py
│   ├── static
│   │   ├── css
│   │   │   └── fashion.css
│   │   ├── img
│   │   │   └── emblem.png
│   │   ├── js
│   │       └── app.js
│   ├── templates
│   │   ├── about.html
│   │   ├── ...
│   │   └── index.html
│   ├── checks.py
│   ├── urls.py
│   └── views.py
├── mission
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── static
├── handle.py
├── readme.md
├── necessities.txt
└── uwsgi.ini

The previous request cycle regarded like this:

the online shopper <-> NGINX <-> socket <-> uwsgi <-> Django

Django sits behind a UWSGI server which runs 4 staff. UWSGI creates a socket that’s used to speak with NGINX. NGINX acts as a reverse proxy and passes all new requests right down to UWSGI by the socket. All of this was operating on the host server.

Previous wsgi.py & uwsgi.ini

The contents of the default wsgi.py file created by Django have been:

WSGI config for ______ mission.
It exposes the WSGI callable as a module-level variable named ``software``.
For extra info on this file, see

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fci_django.settings")

from django.core.wsgi import get_wsgi_application
software = get_wsgi_application()

And my uwsgi.ini file regarded like this:

mission = mission
base = /mission
socket_dir = %(base)

chdir = %(base)
module = %(mission).wsgi:software

grasp = true
processes = 5

socket = %(socket_dir)/%(mission).sock
vacuum = true
daemonize = /var/log/uwsgi/mission.log

I used to be operating UWSGI utilizing the next command:

uwsgi --ini ~/app/uwsgi.ini

This created a socket known as mission.sock which was utilized by NGINX to ship visitors to it. This socket was made contained in the /mission folder.

Previous NGINX server configuration

My NGINX configuration contained the next content material:

server {
    server_name mydomain.com www.mydomain.com;

    access_log /var/log/nginx/mission.entry.log;
    error_log /var/log/nginx/mission.error.log;
    location = /favicon.ico { access_log off; log_not_found off; }

    location /static/ {
        root /dwelling/yasoob/mission;

    location /media/ {
        alias /dwelling/yasoob/mission/static/;

    location / {
        embrace         uwsgi_params;
        uwsgi_pass      unix:/dwelling/yasoob/mission/mission.sock;

When you don’t find out about NGINX server configuration recordsdata, they normally reside in /and so forth/nginx/sites-available/ folder after which symlinked to /and so forth/nginx/sites-enabled/ folder. Let me break down this configuration file:

  1. server_name makes positive that if we’re serving a number of domains/sub-domains from the identical server, NGINX is aware of which area this specific config offers with. You may have a number of server blocks in a single file. That’s truly how SSL redirection works. I’ll speak about that later as soon as we now have the fundamental setup working.
  2. access_log and error_log inform NGINX the place to save lots of the log recordsdata for this specific web site/app. When you don’t specify this then the logs are saved in /var/log/nginx/entry.log and /var/log/nginx/error.log
  3. access_log off; and log_not_found off; inform NGINX to not log entry and error logs for this specific endpoint
  4. location /static/ mapping tells NGINX to seek out the static recordsdata within the project_name dir. It’s the identical with location /media/. NGINX does caching fairly properly and is best suited to serving static recordsdata.
  5. embrace uwsgi_params tells NGINX to load the uwsgi_params file. It’s distributed with the NGINX distribution and appears one thing like this.
  6. uwsgi_pass tells NGINX to move the incoming connection request to the mission.sock socket. UWSGI might be listening to this socket on the different finish.

PostgreSQL vs SQLite

One other essential element is that I used to be utilizing a sqlite DB for this mission. It was very low visitors and a small web site. This determination made it barely simpler for me to port this mission to docker. It isn’t exhausting to port Postgresql or another DB nevertheless it requires a bit further configuration which I didn’t must do.

Placing Django in a Container

Within the new setup, the entire request cycle regarded the identical with only one minor distinction. UWSGI and Django will now be operating in a container and UWSGI will create a socket file in a quantity shared with the host.

For this new request cycle, I solely needed to make one main change. I needed to be sure that UWSGI was making the socket in a shared folder. The uwsgi.ini file ended up being precisely the identical because the previous one however with two essential modifications:

socket_dir = /shared
chmod-socket = 666

This /shared listing might be mounted from the host machine and I’ll present you ways to try this in only a second. We additionally change the permissions of the socket to 666 so that everybody can learn and write to it. This was essential as a result of I used to be getting the next error with out it:

2014/02/27 14:20:48 [crit] 29947#0: *20 join() to unix:///tmp/uwsgi.sock failed (13: Permission denied) whereas connecting to upstream, shopper:, server: mission.com, request: "GET / HTTP/1.1", upstream: "uwsgi://unix:///tmp/mission.sock:", host: "www.mission.com"

I obtained the chmod 666 resolution from this StackOverflow reply.

The NGINX conf modified barely as properly. I needed to replace the uwsgi_pass property and level it to the folder which was going to be shared with the Docker container. I simply set it to:

uwsgi_pass      unix:/dwelling/yasoob/shared/mission.sock;

The very subsequent step was to write down a Dockerfile. That is what I got here up with:

FROM python:2

RUN apt-get replace && 
    apt-get set up -y build-essential python vim net-tools && 
    pip set up uwsgi

COPY . /app/
RUN pip set up --no-cache-dir -r /app/necessities.txt

CMD [ "uwsgi", "--ini", "/app/uwsgi.ini" ]

This Dockerfile relies on the Python 2 picture. The steps on this docker file are:

  1. Set up the required Python packages, uwsgi and useful instruments for debugging
  2. Workdir / specifies that we are going to be working within the / dir
  3. Copy every part from the present listing on the host to the /app listing within the container
  4. Set up all of the required dependencies utilizing pip and the necessities.txt file
  5. Run uwsgi till the container terminates for some purpose

After creating the Dockerfile it was only a matter of constructing the Docker picture earlier than I might use it to run a container:

docker construct -t mission .

The docker run command is a bit concerned. That is what I used:

docker run --rm -d --name mission 
        -v ~/shared:/shared 
        -v ~/mission/static:/app/static 
        --network='host' --restart unless-stopped mission

There are a few issues occurring right here:

  1. --rm tells Docker to scrub up the container on termination
  2. -d makes the container run in daemon mode
  3. -v ~/shared:/shared mounts the ~/shared folder from my host machine to the /shared folder within the container
  4. -v ~/mission/static:/app/static mounts the static dir from the host to container. That is the place all of the static belongings of the web site (together with uploads) are saved. NGINX serves the static recordsdata from this listing
  5. --network='host' simply makes positive that Docker runs this on the host community and doesn’t create a separate community for this container. I believe I might have gotten away with out this selection actually
  6. --restart unless-stopped makes positive that Docker restarts this container except the consumer explicitly terminated it

Turning on SSL

I used certbot for organising SSL for the web site. The set up is tremendous easy on Ubuntu (and I assume all different Linux distros). I simply had so as to add the certbot/certbot ppa to my sources and do a easy set up:

sudo add-apt-repository ppa:certbot/certbot
sudo apt set up python-certbot-nginx

Then I simply ran certbot and adopted on-screen directions:

sudo certbot --nginx -d mission.com

When certbot requested me if it ought to arrange a redirect from HTTP to HTTPS I stated sure:

Please select whether or not or to not redirect HTTP visitors to HTTPS, eradicating HTTP entry.
1: No redirect - Make no additional modifications to the webserver configuration.
2: Redirect - Make all requests redirect to safe HTTPS entry. Select this for
new websites, or should you're assured your website works on HTTPS. You may undo this
change by modifying your net server's configuration.
Choose the suitable quantity [1-2] then [enter] (press 'c' to cancel):

This up to date my mission file in /and so forth/nginx/sites-available listing and added a 301 redirect server block.

Now I simply went to mission.com to check whether or not every part was working or not and to my shock it was! It solely took me a day filled with debugging to get it to work however on the finish of the day it did work and that’s all that issues ?

You may learn extra detailed directions for methods to arrange certbot on Digital Ocean.

I’m positive I will need to have made a few errors throughout this port however the web site was working and NGINX wasn’t complaining and that’s all that mattered. You probably have any feedback or questions be happy to ask them within the feedback under. I’ll see you within the subsequent publish! ? ❤️