All About Django

Everything related to Django

Deploying Django applications in production with uwsgi and nginx

--

This blog post is about setting up a Django application which uses uWSGI as app server and Nginx as the web server.

We will assume the following dependencies about the environment.

  • Ubuntu 18.04
  • Python 3
  • Django > 2.0 (here Django 2.1)

For this project, we will use a Django application which shows the list of all marvel movies. It contains a single URL which looks like the following.

Django Movie App

The code for the project can be found here.

Let’s assume that we have just launched a new instance and there is nothing set up on the instance. We will begin by checking the version of the Ubuntu.

$ lsb_release -aDistributor ID: Ubuntu
Description: Ubuntu 18.04.2 LTS
Release: 18.04
Codename: bionic

We will use a user named ubuntu which has sudo access.

Now we will install python3 and virtualenv on the instance.

sudo apt update
sudo apt install -y python3-pip
sudo apt install build-essential libssl-dev libffi-dev python3-dev
sudo pip3 install virtualenv

We will now clone the repository in the home folder of ubuntu user.

cd ~
git clone https://github.com/all-about-django/django-uwsgi-nginx-tutorial.git

Now follow the project installation steps. This can vary from project to project but at the end, we have to make sure that we are using a virtual environment and we are able to execute runserver on the instance.

python manage.py runserver# runserver output
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).May 08, 2019 - 03:52:06
Django version 2.2.1, using settings 'movies.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Now we will install uWSGI. We will also create directories to store uWSGI config files for various projects.

sudo pip3 install uwsgi
sudo mkdir -p /etc/uwsgi/apps-available /etc/uwsgi/apps-enabled

Now we will create a uWSGI config file in /etc/uwsgi/apps-available called movies.ini . We will need sudo permission to create the file.

# create file
sudo vim /etc/uwsgi/apps-available/movies.ini

The contents of the file will look like

[uwsgi]
uid = ubuntu
gid = ubuntu
chdir = /home/ubuntu/django-uwsgi-nginx-tutorial/movies
home = /home/ubuntu/django-uwsgi-nginx-tutorial/pyenv
module = movies.wsgi:application
env = DJANGO_SETTINGS_MODULE=movies.settings
master = true
processes = 3
socket = /run/uwsgi/movies.sock
# logto = /var/log/uwsgi/movies.log
chown-socket = ubuntu:ubuntu
chmod-socket = 664
vacuum = true

Note that we have commented out logto directive. This is because sometimes we need to debug why is uWSGI not working and if the directory permission for the log directory is not correct, then we may face some error. Hence first we will check if the uWSGI is working fine and then uncomment the logto directive.

We will now create the directory to hold socket file.

cd /run/
sudo mkdir -p uwsgi
sudo chown -R ubuntu:ubuntu uwsgi/

Now we will check if this config is working properly or not.

cd /etc/uwsgi/apps-available/
uwsgi --ini movies.ini

If everything is correct, then we should see something similar on the terminal.

*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 11469)
spawned uWSGI worker 1 (pid: 11471, cores: 1)
spawned uWSGI worker 2 (pid: 11472, cores: 1)
spawned uWSGI worker 3 (pid: 11473, cores: 1)

Now that the uWSGI config is working, we will create directories to hold log files.

cd /var/log
sudo mkdir -p uwsgi
sudo chown -R ubuntu:ubuntu uwsgi/

Now uncomment the logto directory in movies.ini . The final contents of the file will look like

[uwsgi]
uid = ubuntu
gid = ubuntu
chdir = /home/ubuntu/django-uwsgi-nginx-tutorial/movies
home = /home/ubuntu/django-uwsgi-nginx-tutorial/pyenv
module = movies.wsgi:application
env = DJANGO_SETTINGS_MODULE=movies.settings
master = true
processes = 3
socket = /run/uwsgi/movies.sock
logto = /var/log/uwsgi/movies.log
chown-socket = ubuntu:ubuntu
chmod-socket = 664
vacuum = true

Now when we run the check whether the config is working properly or not, we will look logs in /var/log/uwsgi/movies.log instead of the current terminal screen.

sudo tail -10f /var/log/uwsgi/movies.log# log file contents
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 11612)
spawned uWSGI worker 1 (pid: 11614, cores: 1)
spawned uWSGI worker 2 (pid: 11615, cores: 1)
spawned uWSGI worker 3 (pid: 11616, cores: 1)

This shows that uWSGI config is correct. Now let’s enable this config. We will create a symlink for this in apps-enabled directory.

sudo ln -sf /etc/uwsgi/apps-available/movies.ini /etc/uwsgi/apps-enabled/# check if the symlink is properly created
ls -l /etc/uwsgi/apps-enabled/

We create a symlink and not copy or move the original file since this way all the files are kept at a central place and we can just enable/disable any config instantly by deleting its config and restarting uWSGI.

Now at this point, uWSGI is working fine. We just need to run it in a service manner, so that it can keep running in the background and also starts when the server boots up. The service will be located at /etc/systemd/system/uwsgi.service .

sudo vim /etc/systemd/system/uwsgi.service

The contents of the service file will look like

[Unit]
Description=uWSGI Emperor service
[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown ubuntu:ubuntu /run/uwsgi'
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/apps-enabled
Restart=always #make sure the server is running
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target

Now we will start the uWSGI service and verify by px if the service is running or not.

sudo systemctl start uwsgi# check uwsgi status
ps aux | grep uwsgi
# if we see several process, it means uWSGI is working fine. Else we need to debug.

At this point, we have successfully connected uWSGI and the application. Next, we need to connect Nginx and uWSGI so that the setup is complete.

We will begin by installing Nginx.

sudo apt install -f nginx-full

When Nginx is installed it runs as a process owned and controlled by www-data . We will change this to ubuntu user.

We will edit the file /etc/nginx/nginx.conf and replace www-data with ubuntu user (the first line only).

# sudo vim /etc/nginx/nginx.confuser ubuntu;      # instead of www-data

Now, we will write an Nginx config for this. We will place this file in /etc/nginx/sites-available . Let's call this file as movies .

sudo vim /etc/nginx/sites-available/movies

The file will look like

server{
listen 80;
server_name server_ip_address_or_domain_name;
location / {
include uwsgi_params;
uwsgi_pass unix:/run/uwsgi/movies.sock;
}
location /static {
root /home/ubuntu/django-uwsgi-nginx-tutorial/movies;
}
location /media {
root /home/ubuntu/django-uwsgi-nginx-tutorial/movies;
}
}

Next, we will create a symlink for this in /etc/nginx/sites-enabled directory.

sudo ln -s /etc/nginx/sites-available/movies /etc/nginx/sites-enabled/# check if the symlink is properly created or notls -l /etc/nginx/sites-enabled/

One thing to note here is the default config is also present in thesites-enabled directory. This config listens on port 80 so we need to remove this.

Now, we will check whether the Nginx config is correct or not by running the following

sudo nginx -t

Now we will restart the uWSGI and start the Nginx server

sudo systemctl restart uwsgi
sudo service nginx start

Now we will visit our server IP, and see if the app is working or not.

If we see a Invalid HTTP_HOST header then we need to edit ALLOWED_HOSTS in project settings.

If we face any other error, then we need to see Nginx and uWSGI log files and accordingly.

sudo tail -10f /var/log/nginx/access.logsudo tail -10f /var/log/nginx/error.logsudo tail -10f /var/log/uwsgi/movies.log

Conclusion

In this tutorial, we learned how to deploy a Django application in the production environment using uWSGI as the app server and Nginx as the web server.

If you like my article, consider buying me a coffee here

--

--

Responses (2)