How To Serve Multiple Django Applications with uWSGI and Nginx in Ubuntu 20.04

django apps + uwsgi + sockets + systemd + ubuntu
Image: django apps + uwsgi + sockets + systemd + ubuntu (License: CC-BY-SA-NC Marcelo Canina)

Setup a webserver to serve Django apps

Published:
Last modified:

Overview

This is a concise guide to have an Ubuntu server with multiple Django apps.

In this way we can easily have a Virtual Private Server and set it up to be our server for Django projects.

For this we will use:

  • A web server, nginx, to process incoming requests from clients.
  • Communication between web server and Django with uWSGI via sockets
  • Using uWSGI as a systemd job, the preferred way to start processes in Ubuntu.

Users

If you just have a root user, we are going to create another one called ubuntu to handle the apps properly.

This step can be skipped if you already have an user, just make sure to use the right one in the following steps.


$ useradd -m -s /bin/bash ubuntu
$ passwd ubuntu

Now we have the Linux user ubuntu.

Django apps

We expect a Django app with:

  • a PIP /requirements.txt file which list the Django packages used in the app
  • a settings file for production like /myproject_first/settings/production.py

Code

Put your Django apps in /opt/apps.


$ mkdir /opt/apps
$ chown -R ubuntu:ubuntu /opt/apps

From your server, copy your projects there, e.g.: rsync -avP /django/local/myproject_first new-server:/opt/apps

For this guide, we consider having two Django projects:

  • /opt/apps/myproject_first and
  • /opt/apps/myproject_second

Virtual envs

Create each virtual environment under your Linux user home directory.


$ apt-get install python3-venv

Then create the virtual environment for each Django app:


$ su ubuntu
$ python3 -m venv ~/.virtualenvs/myproject_first
$ source ~/.virtualenvs/myproject_first/bin/activate
(myproject_first)$ 

And install each requirements (note that we have already activated the virtual environment):


(myproject_first)$ pip install -r /opt/apps/myproject_first/requirements.txt

Application server

The uWSGI1 (Web Server Gateway Interface) project implements the wsgi2 standard which defines an interface between web servers and Python web applications, and also provides process managers and monitors.

Install

We are going to create a uwsgi server in its emperor mode.

Install uWSGI globally, avoid installing it inside a virtual environment.

sudo pip install uwsgi

Configure uwsgi

From all the features of uWSGI, we will use the multi-app deployment feature called: The uWSGI Emperor3.

It is a special uWSGI instance that will monitor specific events and will spawn/stop/reload instances (known as vassals, when managed by an Emperor) on demand.

We need to create configuration files for each Django app. These configurations will be placed in /etc/uwsgi/vassals so the daemon can detect any changes to them and act accordingly.

Create the above directory:


# mkdir --parents /etc/uwsgi/vassals

And a uWSGI ini configuration file /etc/uwsgi/emperor.ini with the following content:

[uwsgi]
emperor = /etc/uwsgi/vassals

This way we can call start the server and tell to monitor the vassals directory for new configuration files, e.g. /usr/local/bin/uwsgi –ini /etc/uwsgi/emperor.ini.

systemd

To handle system processes and daemons, Ubuntu relies on systemd, which is “a system and session manager for Linux, compatible with SysV and LSB init scripts.”4

To have our uwsgi server daemon, we will create a systemd script so it can be handled with the systemctl command.

Create a systemd script: https://uwsgi-docs.readthedocs.io/en/latest/Systemd.html

In /etc/systemd/system/emperor.uwsgi.service:

[Unit]
Description=uWSGI Emperor service
After=syslog.target

[Service]
ExecStart=/usr/local/bin/uwsgi --ini /etc/uwsgi/emperor.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
RuntimeDirectory=uwsgi

[Install]
WantedBy=multi-user.target

Add vassals

For each Django app, add an ini file in /etc/uwsgi/vassals/ with its uwsgi configuration, in this case for the app /opt/app/myproject_first we will create the file /etc/uwsgi/vassals/myproject_first.ini:

Note: If you haven’t already done it, we need to have the new server listed in the ALLOWED_HOSTS setting, this can be done by having the following setting: ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS').split(',') if os.getenv('DJANGO_ALLOWED_HOSTS') else [] and then setting the environment variable DJANGO_ALLOWED_HOSTS with the server’s ip.

# myproject_first.ini file
[uwsgi]

project = myproject_first
uid = ubuntu
gid = www-data

##########
# DJANGO #
##########

# Django's full path base directory
chdir           = /opt/apps/%(project)
# Django's wsgi
module          = %(project).wsgi:application
# Virtualenv full path
home            = /home/ubuntu/.virtualenvs/%(project)
env = DJANGO_SETTINGS_MODULE=%(project).settings.production
#env = DJANGO_ALLOWED_HOSTS=192.81.xx.xx

###########
# PROCESS #
###########

master          = true
# maximum number of workers
processes       = 1 #10, 5
socket = /run/uwsgi/%(project).sock
chown-socket = %(uid):%(gid)
chmod-socket = 660

# clear environment on exit
vacuum          = true

pidfile=/tmp/uwsgi_%(project).pid
harakiri=20
max-requests=5000

Web server

Install

Install nginx.


$ sudo apt install nginx

Virtual Host

Create a virtual host for each Django app, in /etc/nginx/sites-available/myproject_first.conf

upstream django_myproject_first {
    server unix:///run/uwsgi/myproject_first.sock;
}

# configuration of the server
server {
    # the port your site will be served on
    listen      80;
    # the domain name it will serve for
    server_name myproject_first.com;

    charset     utf-8;

    # max upload size
    client_max_body_size 75M;

    # # Django media
    # location /media  {
    #     alias /path/to/your/mysite/media;
    # }

    # location /static {
    #     alias /path/to/your/mysite/static;
    # }

    # non-media requests handled by uwsgi
    location / {
        uwsgi_pass  django_myproject_first;
        include     /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
    }
}

Test everything is fine: nginx -t.

Then enable the new site:


$ ln -s /etc/nginx/sites-available/myproject_first.conf /etc/nginx/sites-enabled/

If you choose to serve static and media files with nginx, you should add it using the alias directive.

Start servers

Now start the server and check its status, using root user.:


# systemctl start nginx.service
# systemctl daemon-reload; systemctl restart emperor.uwsgi.service
# systemctl status emperor.uwsgi.service
● emperor.uwsgi.service - uWSGI Emperor service
     Loaded: loaded (/etc/systemd/system/emperor.uwsgi.service; disabled; vendor preset: enabled)
     Active: active (running) since Mon 2020-10-26 08:55:30 -03; 5s ago
   Main PID: 1620374 (uwsgi)
     Status: "The Emperor is governing 1 vassals"
      Tasks: 3 (limit: 1137)
     Memory: 36.8M
     CGroup: /system.slice/emperor.uwsgi.service
             β”œβ”€1620374 /usr/local/bin/uwsgi --ini /etc/uwsgi/emperor.ini
             β”œβ”€1620375 /usr/local/bin/uwsgi --ini myproject_first.ini
             └─1620388 /usr/local/bin/uwsgi --ini myproject_first.ini

Oct 26 08:55:30 peachepe uwsgi[1620375]: your server socket listen backlog is limited to 100 connections
Oct 26 08:55:30 peachepe uwsgi[1620375]: your mercy for graceful operations on workers is 60 seconds
Oct 26 08:55:30 peachepe uwsgi[1620375]: mapped 145808 bytes (142 KB) for 1 cores
Oct 26 08:55:30 peachepe uwsgi[1620375]: *** Operational MODE: single process ***
Oct 26 08:55:31 peachepe uwsgi[1620375]: WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x55973cd886d0 pid: 1620375 (default app)
Oct 26 08:55:31 peachepe uwsgi[1620375]: *** uWSGI is running in multiple interpreter mode ***
Oct 26 08:55:31 peachepe uwsgi[1620375]: spawned uWSGI master process (pid: 1620375)
Oct 26 08:55:31 peachepe uwsgi[1620374]: Mon Oct 26 08:55:31 2020 - [emperor] vassal myproject_first.ini has been spawned
Oct 26 08:55:31 peachepe uwsgi[1620375]: spawned uWSGI worker 1 (pid: 1620388, cores: 1)
Oct 26 08:55:31 peachepe uwsgi[1620374]: Mon Oct 26 08:55:31 2020 - [emperor] vassal myproject_first.ini is ready to accept requests

And our server is ready to accept requests.

Permament changes

Make the Systemd service to start after reboot by enabling the service:


# systemctl enable emperor.uwsgi.service
Created symlink /etc/systemd/system/multi-user.target.wants/emperor.uwsgi.service β†’ /etc/systemd/system/emperor.uwsgi.service.

Update code

Every time we publish a change into the production server, we need to gracefully restart uWSGI.

As we have used pidfile=/tmp/uwsgi_%(project).pid in /etc/uwsgi/vassals/myproject_first.ini, then we need to touch5 use that pid file and do uwsgi --reload /tmp/uwsgi_myproject_first.pid.

Other options to restart the server are6:

# using kill to send the signal
kill -HUP `cat /tmp/project-master.pid`
# or the convenience option --reload
uwsgi --reload /tmp/project-master.pid
# or if uwsgi was started with touch-reload=/tmp/somefile
touch /tmp/somefile

Conclusion

After finishing the guide we will have:

  • A Linux user called ubuntu.
  • Web server receiving request at port 80.
  • The following structure:
.
β”œβ”€β”€ etc
β”‚Β Β  β”œβ”€β”€ nginx
β”‚Β Β  β”‚Β Β  └── sites-available
β”‚Β Β  β”‚Β Β      β”œβ”€β”€ myproject_first.conf
β”‚Β Β  β”‚Β Β      └── myproject_second.conf
β”‚Β Β  β”œβ”€β”€ systemd
β”‚Β Β  β”‚Β Β  └── system
β”‚Β Β  β”‚Β Β      └── emperor.uwsgi.service
β”‚Β Β  └── uwsgi
β”‚Β Β      β”œβ”€β”€ emperor.ini
β”‚Β Β      └── vassals
β”‚Β Β          β”œβ”€β”€ myproject_first.ini
β”‚Β Β          └── myproject_second.ini
β”œβ”€β”€ home
β”‚Β Β  └── ubuntu
β”‚Β Β      └── .virtualenvs
β”‚Β Β          β”œβ”€β”€ myproject_first
β”‚Β Β          └── myproject_second
└── opt
    └── apps #django src
        β”œβ”€β”€ myproject_first
        └── myproject_second

And sockets ready:


# ls -lrtad /run/uwsgi /run/uwsgi/*
srw-rw---- 1 ubuntu www-data  0 Oct 26 08:55 /run/uwsgi/myproject_first.sock
drwxr-xr-x 2 root     root     60 Oct 26 08:55 /run/uwsgi

References

Uruguay
Marcelo Canina
I'm Marcelo Canina, a developer from Uruguay. I build websites and web-based applications from the ground up and share what I learn here.
comments powered by Disqus


Guide to have a dedicated Ubuntu web server for serving Django apps using nginx and uwsgi with sockets

Clutter-free software concepts.
Translations English EspaΓ±ol

Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 . Terms and Policy.

Powered by SimpleIT Hugo Theme

·