Tech News

How To Deploy Django with Postgres, Nginx, and Gunicorn on Debian 10


Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.



This tutorial will walk you through the steps to install and configure some required dependencies to set up Django on a Debian 10 server.

Prerequisites

To follow this guide, you will need one Debian 10 (physical or virtual) machine should have a non-root user with sudo privileges.

Installing Dependencies

You need to install following required dependencies using the below command:

sudo apt update
sudo apt install -y python3-pip python3-dev libpq-dev

Installing Nginx

You can install Nginx web server from the Debian default repository using the below command:

sudo apt install -y nginx curl

Installing PostgreSQL

Type the following command on your Debian 10 machine's terminal to install postgresql database:

sudo apt install -y postgresql postgresql-contrib

Creating Database

You must replace all the highlighted parameters in this guide to reflect yours. Now start creating a database and database user for your Django application like below:

sudo -u postgres psql
create database testproject;
create user testprojectuser with password 'TypePasswordHere';
alter role testprojectuser set client_encoding to 'utf8';
alter role testprojectuser SET default_transaction_isolation to 'read committed';
alter role testprojectuser set timezone to 'UTC';
grant all privileges on database testproject to testprojectuser;
\q

Postgres is now set up so that Django can connect to and manage its database information.

Creating Python Virtual Environment

You can set up Python requirements within a virtual environment for easier management.

sudo -H pip3 install --upgrade pip
sudo -H pip3 install virtualenv

mkdir ~/testprojectdir
cd ~/testprojectdir

virtualenv testprojectenv

Output
Using base prefix '/usr'
New python executable in /home/peter/testprojectdir/testprojectenv/bin/python3
Also creating executable in /home/peter/testprojectdir/testprojectenv/bin/python
Installing setuptools, pip, wheel...
done.

source testprojectenv/bin/activate
pip install django gunicorn psycopg2-binary

You should now have all of the required software in place needed to start a Django project.

Creating New Django Project

Since we already have a project directory, we will tell Django to install the files here. It will create a second level directory with the actual code, which is normal, and place a management script in this directory. The key to this is that we are defining the directory explicitly instead of allowing Django to make decisions relative to our current directory:

django-admin.py startproject testproject ~/testprojectdir

Adjust the Project Settings Parameters

The first thing you should do with your newly created project files is adjust the settings. Open the settings file in your favorite text editor and add, update and replace the following parameters to reflect yours:

nano ~/testprojectdir/testproject/settings.py

ALLOWED_HOSTS = ['labserver.techsupportpk.com', 'localhost']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'testproject',
        'USER': 'testprojectuser',
        'PASSWORD': 'TypePasswordHere',
        'HOST': 'localhost',
        'PORT': '',
    }
}

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

Save and close the file when you are done.

Finishing Initial Project Setup

Now, you can migrate the initial database schema to your PostgreSQL database using the management script:

~/testprojectdir/manage.py makemigrations
~/testprojectdir/manage.py migrate

Output
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK

Create an administrative user for the project by typing:

~/testprojectdir/manage.py createsuperuser

Output
Username (leave blank to use 'peter'):
Email address: peter@techsupportpk.com
Password:
Password (again):
Superuser created successfully.

You will have to select a username, provide an email address, and choose and confirm a password.

You can collect all of the static content into the directory location you configured by typing:

~/testprojectdir/manage.py collectstatic

Output
119 static files copied to '/home/peter/testprojectdir/static'.

You will have to confirm the operation. The static files will then be placed in a directory called static within your project directory.

If UFW firewall protecting your Debian 10 server, you'll have to allow access to the port in order to test the development server.

sudo ufw allow 8000

Now you can test your project by starting up the Django development server with the following command:

~/testprojectdir/manage.py runserver 0.0.0.0:8000

Output
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
July 31, 2019 - 10:13:28
Django version 2.2.3, using settings 'testproject.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

Open up your web browser, access your server's name or IP address followed by port like below:

http://server_name_or_IP:8000

You should see the following default Django index page:



If you append /admin to the end of the URL in the address bar, you will be prompted for the administrative username and password you created earlier with the createsuperuser command:


Enter your username and password to log in to the Django admin interface. In our case, username is peter


After authenticating, you will see the default Django admin interface:



When you are finished exploring, press CTRL-C in the Debian 10 terminal window to stop the development server.


Testing Gunicorn

You should test Gunicorn functionality to make sure that it can serve the application. You can do this by entering your project directory and using gunicorn to load the project's WSGI module:

cd ~/testprojectdir
gunicorn --bind 0.0.0.0:8000 testproject.wsgi

This will start Gunicorn on the same interface that the Django development server was running on. You can go back and test the app again.


When you are finished testing, press CTRL-C in the terminal window to stop Gunicorn.

You are now finished configuring your Django application. You can back out of our virtual environment by typing:

deactivate

The virtual environment indicator in your prompt will be removed.

Creating Systemd Socket and Service Files for Gunicorn

The Gunicorn socket will be created at boot and will listen for connections. When a connection occurs, systemd will automatically start the Gunicorn process to handle the connection.

Start by creating and opening a systemd socket file for Gunicorn with sudo privileges:

sudo nano /etc/systemd/system/gunicorn.socket

Inside, we will create a [Unit] section to describe the socket, a [Socket] section to define the socket location, and an [Install] section to make sure the socket is created at the right time:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Save and close the file when you are done.

Next, create and open a systemd service file for Gunicorn with sudo privileges in your text editor. The service filename should match the socket filename with the exception of the extension:

sudo nano /etc/systemd/system/gunicorn.service

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

[Service]
User=peter
Group=www-data
WorkingDirectory=/home/peter/testprojectdir
ExecStart=/home/peter/testprojectdir/testprojectenv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          testproject.wsgi:application

[Install]
WantedBy=multi-user.target

Save and close it now.

You can start and enable the Gunicorn socket. This will create the socket file at /run/gunicorn.sock now and at boot. When a connection is made to that socket, systemd will automatically start the gunicorn.service to handle it:

sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

You can confirm that the operation was successful by checking for the socket file.

sudo systemctl status gunicorn.socket

Output
gunicorn.socket - gunicorn socket
   Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor preset: enabled)
   Active: active (listening) since Wed 2019-07-31 15:53:14 PKT; 14s ago
   Listen: /run/gunicorn.sock (Stream)
    Tasks: 0 (limit: 1147)
   Memory: 0B
   CGroup: /system.slice/gunicorn.socket

Jul 31 15:53:14 labserver systemd[1]: Listening on gunicorn socket.


Next, check for the existence of the gunicorn.sock file within the /run directory:

file /run/gunicorn.sock

Output
/run/gunicorn.sock: socket

If the systemctl status command indicated that an error occurred or if you do not find the gunicorn.sock file in the directory, it's an indication that the Gunicorn socket was not able to be created correctly. You must fix any problems before continuing.

Testing Socket Activation

Currently, if you've only started the gunicorn.socket unit, the gunicorn.service will not be active yet since the socket has not yet received any connections. You can check this by typing:

sudo systemctl status gunicorn

Output
● gunicorn.service - gunicorn daemon
   Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

To test the socket activation mechanism, you can send a connection to the socket through curl by typing:

curl --unix-socket /run/gunicorn.sock localhost

You should see the HTML output from your application in the terminal. This indicates that Gunicorn was started and was able to serve your Django application. You can verify that the Gunicorn service is running by typing:

sudo systemctl status gunicorn

Output
● gunicorn.service - gunicorn daemon
   Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
   Active: active (running) since Wed 2019-07-31 15:55:39 PKT; 27s ago
 Main PID: 15091 (gunicorn)
    Tasks: 4 (limit: 1147)
   Memory: 85.1M
   CGroup: /system.slice/gunicorn.service
           ├─15091 /home/peter/testprojectdir/testprojectenv/bin/python3 /home/peter/testprojectdir/testprojec
           ├─15094 /home/peter/testprojectdir/testprojectenv/bin/python3 /home/peter/testprojectdir/testprojec
           ├─15095 /home/peter/testprojectdir/testprojectenv/bin/python3 /home/peter/testprojectdir/testprojec
           └─15096 /home/peter/testprojectdir/testprojectenv/bin/python3 /home/peter/testprojectdir/testprojec

Jul 31 15:55:39 labserver systemd[1]: Started gunicorn daemon.
Jul 31 15:55:39 labserver gunicorn[15091]: [2019-07-31 15:55:39 +0500] [15091] [INFO] Starting gunicorn 19.9.0
Jul 31 15:55:39 labserver gunicorn[15091]: [2019-07-31 15:55:39 +0500] [15091] [INFO] Listening at: unix:/run/
Jul 31 15:55:39 labserver gunicorn[15091]: [2019-07-31 15:55:39 +0500] [15091] [INFO] Using worker: sync
Jul 31 15:55:39 labserver gunicorn[15091]: [2019-07-31 15:55:39 +0500] [15094] [INFO] Booting worker with pid:
Jul 31 15:55:39 labserver gunicorn[15091]: [2019-07-31 15:55:39 +0500] [15095] [INFO] Booting worker with pid:
Jul 31 15:55:40 labserver gunicorn[15091]: [2019-07-31 15:55:40 +0500] [15096] [INFO] Booting worker with pid:
Jul 31 15:55:40 labserver gunicorn[15091]:  - - [31/Jul/2019:10:55:40 +0000] "GET / HTTP/1.1" 200 16348 "-" "c

If the output from curl or the output of systemctl status indicates that a problem occurred, check the logs for additional details:

sudo journalctl -u gunicorn

Check your /etc/systemd/system/gunicorn.service file for problems. If you make changes to the /etc/systemd/system/gunicorn.service file, reload the daemon to reread the service definition and restart the Gunicorn process by typing:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn

Make sure you troubleshoot the above issues before continuing.

Configure Nginx to Proxy Pass to Gunicorn


Now that Gunicorn is set up, you need to configure Nginx to pass traffic to the process.

Start by creating and opening a new server block in Nginx's sites-available directory:

sudo nano /etc/nginx/sites-available/testproject

server {
    listen 80;
    server_name labserver.techsupportpk.com;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/peter/testprojectdir;
    }

location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

Save and close the file when you are done.

Now, you can enable the file by linking it to the sites-enabled directory:

sudo ln -s /etc/nginx/sites-available/testproject /etc/nginx/sites-enabled
sudo nginx -t

Output
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

sudo systemctl restart nginx

Finally, you need to open up your UFW firewall to normal traffic on port 80. Since you no longer need access to the development server, you can remove the rule to open port 8000 as well:

sudo ufw delete allow 8000
sudo ufw allow 'Nginx Full'

You should now be able to access your server's name or IP address to view your application in web browser like below:



Wrapping up

In this tutorial, you've set up a Django project in its own virtual environment. You've configured Gunicorn to translate client requests so that Django can handle them. Afterwards, you set up Nginx to act as a reverse proxy to handle client connections and serve the correct project depending on the client request.

No comments