Configure a Django website on LEMP

It takes some steps to fully install and configure a Python-Django website. On this page, I will summarize the complete workflow on a LEMP-server, using MySQL as database and Gunicorn as WSGI-server (tutorial).

Requirements

Create a new database that has a user with full privileges

Topics on this page

Virtual environment

To work with Python, I will use venv to create the virtual environment and vex to activate it. The packages for pip3 and venv have been installed during the basic configuration. Login as the user that will deploy Django ang install vex.

$ pip3 install --user vex

This will install vex in the root of the user in .locate/bin. Only when you install vex for the first time, you need to add the directory to the global path in ect/environment.

PATH="...:~/.local/bin"

Create a directory for your virtual environments (.virtualenvs) and cd into it. I use the python3 -m venv command to create a virtual environment called env (but you can use any name).

~$ mkdir .virtualenvs
~$ cd .virtualenvs
~/.virtualenvs$ python3 -m venv env
~/.virtualenvs$ cd ..
~$ 

You can now create a root directory for the django project, cd into it and activate env.

~$ mkdir testproject
~$ cd testproject
~/testproject$ vex env
~/testproject$ exit
~/testproject$

When env is activated the subdirectory gets underlined. To leave env type exit.

Creating and Configuring a New Django Project

Cd into testproject, activate env and pip Install Django and other dependencies.

~$ cd testproject
~/testproject$ vex env
~/testproject$ pip install django wheel mysqlclient gunicorn

Create a New Django Project.

~/testproject/django-admin startproject testapp .
~/testproject/mkdir static

Configure settings.py in /home/testuser/testproject/testapp/settings.py.

database
...
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
...
ALLOWED_HOSTS = ['your server IP address', 'webdomain.com']
...
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE'  : 'django.db.backends.mysql',
        'OPTIONS': { 'init_command' : "SET sql_mode = 'STRICT_TRANS_TABLES'",
        },
        'NAME'    : 'test-database',
        'USER'    : 'database-user',
        'PASSWORD': 'password',
        'HOST'    : 'localhost',
        'PORT'    : '',
    },
}
...
TIME_ZONE = 'Europe/Amsterdam'
...
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

You can now migrate the initial database schema to the MySQL database, create a superuser (for admin), collect static files and exit the virtual environment.

~/testproject$ python manage.py makemigrations
~/testproject$ python manage.py migrate
~/testproject$ python manage.py createsuperuser
~/testproject$ python manage.py collectstatic
~/testproject$ exit

Creating systemd Socket and Service Files for Gunicorn

Gunicorn is the Web Server Gateway Interface (WSGI) server that runs the Django project to create the web application. To start Gunicorn automatically, you need to setup a gunicorn.socket and a gunicorn.service file in etc/systemd/system. To setup different website you can choose different names for the .socket and .service files.

sudo touch /etc/systemd/system/gunicorn-test.socket
sudo touch /etc/systemd/system/gunicorn-test.service
Socket
[Unit]
Description=gunicorn-test socket

[Socket]
ListenStream=/run/gunicorn-test.sock

[Install]
WantedBy=sockets.target 
Service

The Gunicorn service points to the gunicorn.socket, the webuser and group, the root location of the project, the virtual environment and the wsgi-file (here located in the projectroot/testapp subdirectory). The choice of number of workers will depent on how heavy the website will be used.

[Unit]
Description=gunicorn daemon
Requires=gunicorn-test.socket
After=network.target

[Service]
User=testuser
Group=www-data
WorkingDirectory=/home/testuser/testproject
ExecStart=/home/testuser/.virtualenvs/env/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn-test.sock \
          testapp.wsgi:application

[Install]
WantedBy=multi-user.target

After creating the two files, you need to start and enable gunicorn.socket.

$ sudo systemctl start gunicorn-test.socket
$ sudo systemctl enable gunicorn-test.socket

To restart the system after you made changes use:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn-test
sudo systemctl status gunicorn-test.socket

NGINXserver block

After setting up the Gunicorn wsgi-server, you can now create an NGINX server block, to point to the wsgi-server and, separately, to any static content. Server blocks are small text-snippets that are included in the main nginx configuration-file. The server block text-files are located at etc/nginx/sites-available/.

You can create a new file in this location with:

$ sudo nano webdomain.com
server {
		
    listen 80;
	server_name website.com www.website.com;
	
    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/testuser/testproject;
    }
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn-test.sock;
    }
}

A location directive is included for /static/ content, located at the root directive. NGINX is refered to the location of gunicorn.sock, to serve the Django-content.

The configuration will take effect after creating a symbolic link in the sites-enabled directory, testing the configuration and reloading the nginx configuration file.

$ sudo ln -s /etc/nginx/sites-available/mydomain.com /etc/nginx/sites-enabled/
$ sudo nginx -t
$ sudo systemctl reload nginx

You should now have an active website that connects the webdomain to the root location of your webcontent on the server. http://webdomain.com should display your Python-Django-content. If anything goes wrong, usually because of typing errors, it can take some puzzling to locate any errors. The tutorial containings some additional commands to help in debugging.

As a final step, make sure to secure http with Certbot.