04
Dec 20

Django + Postgres + nginx + uWSGI = ❤️

To date, I set up a production web service thrice. (Yeah, I am a full stack dev!)

The first time I did many mistakes. That website still works, I maintain and update it, but now it would be difficult to fix things, I would have to stop the service and have some quite stressful time re-configuring it.

The second time I did it more or less right, but the deployment happened in the corporate network of the company that I no longer work for, so that knowledge is now lost to me.

When I tried to do it the third time, I had to recall and google many things again. Now it works, so it is a good moment to write a note to myself in future.

The plan

Five steps to quick and easy deployment by Alex who likes to code:

  1. Install nginx, uwsgi, python3, and postgres. (no-op)
  2. Set up PostgreSQL.
  3. Check out the Django project and set it up.
  4. Configure uWSGI.
  5. Configure nginx.

Setting up prerequisites is literally one call to the package manager (yes, I assume that we are on Linux). Too easy, I cannot write anything else about that. Other parts are longer.

PostgreSQL

As user postgres, run psql. This will take us into the DBMS console. There, run the following:

CREATE DATABASE db-name;
CREATE USER db-user WITH PASSWORD 'password';
ALTER ROLE db-user SET client_encoding TO 'utf8';
ALTER ROLE db-user SET default_transaction_isolation
    TO 'read committed';
ALTER ROLE db-user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE db-name TO db-user;

Most commands above are quite self-explanatory. The ALTER ROLE ones are not mandatory but may be useful.

Now exit the DBMS console and proceed to the next part.

Django

This part is the longest, it is in fact the actual deployment of the project, other parts are just… environment.

  1. Switch to the user that will normally do whatever is required regarding the source code, e.g., updating the checkout, editing the code and project configs, etc. Let it be user project.
  2. Check out the project into the common location for websites, like /var/www on Debian. Let it be /var/www/project (we will need it later a few more times).
  3. Create the virtual Python environment (venv); modern versions of Python can do that themselves via python -m venv {directory name}, I like to have it in the venv directory directly within the Django project, so the full path would be /var/www/project/venv.
  4. Activate the venv and install Python requirements. Do not deactivate it for the next few steps.
  5. Edit the project settings. See remarks below for some ideas about this.
  6. Test that the project is configured properly by running manage.py migrate.
  7. Test even more that the project is configured properly by running manage.py runserver. Terminate the server after it starts normally.
  8. Create the Django superuser by running manage.py createsuperuser. We may deactivate the venv now.

Remarks on preparing the project for deployment

The web service always has some site-specific settings, and putting them under the version control would be a pain. I like the idea of moving these settings into the separate file from the very beginning. Create site_settings.py near the standard settings.py, cut and paste whatever is going to differ between hosts, and then add from .site_settings import * into settings.py. Add site_settings.py into .gitignore.

We definitely want SECRET_KEY, DEBUG, and DATABASES to stay in the site config.

uWSGI

As root, create /etc/uwsgi/apps-available/project.ini (the .ini extension is important!) and put this inside:

[uwsgi]
# The root directory of the Django project.
chdir  = /var/www/project
# The Python module within the project that creates
# the application.  It is already there, built into
# every Django project.
module = project.wsgi
# The venv that should be used.
home   = /var/www/project/venv

# Used to connect to the web server.
socket       = /var/www/project/socket
# uWSGI has plugins for different platforms.
# This may require to install something like
# uwsgi-plugin-python3.
plugins      = python37

# Not important :-D but still needed.
master       = true
processes    = 5
vacuum       = true

Create a link to the newly created config to enable the application:

ln -s /etc/uwsgi/apps-available/project.ini \
        /etc/uwsgi/apps-enabled/project.ini

Restart uwsgi and ensure that it runs with no errors.

nginx

As user project, create /var/www/project/project/uwsgi_params and put there this:

uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_ADDR $server_addr;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

(This is in fact the standard file that is shipped with the nginx distribution.)

Now switch to root, create /etc/nginx/sites-available/project and put this inside:

server {
    listen      80;
    server_name project.org;
    charset     utf-8;

    location /static/ {
        alias /var/www/project/static/;
    }

    location / {
        \# Connection to uWSGI.
        uwsgi_pass unix:/var/www/project/socket;
        include    /var/www/project/project/uwsgi_params;
    }
}

Create a link to the newly created config to enable the website:

 ln -s /etc/nginx/sites-available/project \
    /etc/nginx/sites-enabled/project

Restart nginx and ensure that it runs without errors.

That is it!

References