BLOG

Deploy Django project with Gunicorn, PostgreSQL, Nginx

Django is full of shortcuts to make web developers’ lives easier, but all those tools are of no use if you can’t easily deploy your sites. Since Django’s inception, ease of deployment has been a major goal.


There are many options for deploying your Django application, based on your architecture or your particular business needs, but that discussion is outside the scope of what Django can give you as guidance.


Django, being a web framework, needs a web server in order to operate. And since most web servers don’t natively speak Python, we need an interface to make that communication happen.


Django currently supports two interfaces: WSGI and ASGI.


- WSGI is the main Python standard for communicating between web servers and applications, but it only supports synchronous code.
- ASGI is the new, asynchronous-friendly standard that will allow your Django site to use asynchronous Python features, and asynchronous Django features as they are developed.


You should also consider how you will handle static files for your application, and how to handle error reporting.


Finally, before you deploy your application to production, you should run through our deployment checklist to ensure that your configurations are suitable. How to deploy Django.

 

Upgrade system and install needed packages (dependencies):

sudo apt update && sudo apt upgrade -y
sudo apt install python3-pip python3-dev python3-venv libpq-dev postgresql postgresql-contrib nginx curl git -y

 

Configure PostgreSQL.

Open PostgreSQL console:

sudo -u postgres psql

 

Create database:

postgres=# CREATE DATABASE myproject;

 

Create user for project:

postgres=# CREATE USER myprojectuser WITH PASSWORD 'my_secure_password';

 

Setup client_encoding, default_transaction_isolation and timezone:

postgres=# ALTER ROLE myprojectuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE myprojectuser SET timezone TO 'UTC';

 

Grant privileges for created database to user:

postgres=# GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;

 

If you get permission denied error like this:

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/xinit/dev/homepage/manage.py", line 21, in <module>
    main()
  File "/home/xinit/dev/homepage/manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/core/management/base.py", line 89, in wrapped
    res = handle_func(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/core/management/commands/migrate.py", line 244, in handle
    post_migrate_state = executor.migrate(
                         ^^^^^^^^^^^^^^^^^
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/db/migrations/executor.py", line 91, in migrate
    self.recorder.ensure_schema()
  File "/home/xinit/dev/homepage/env/lib/python3.11/site-packages/django/db/migrations/recorder.py", line 70, in ensure_schema
    raise MigrationSchemaMissing("Unable to create the django_migrations table (%s)" % exc)
django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table (permission denied for schema public
LINE 1: CREATE TABLE "django_migrations" ("id" serial NOT NULL PRIMA...
                     ^
)

 

Here is solution:

postgres=# ALTER DATABASE myproject OWNER TO myprojectuser;

 

Quit from PostgreSQL console:

postgres=# \q

 

Setup project.

Create virtual environment, install Django and create new project:

mkdir ~/myproject
cd ~/myproject
python3 -m venv env
source env/bin/activate
pip3 install django
django-admin startproject myproject .

 

Or clone exist and install requirements:

git clone https://gitlab.com/username/myproject.git 
cd myproject
python3 -m venv env
source env/bin/activate
pip3 install -r requirements.txt

 

Install gunicorn and psycopg2-binary:

(env) $ pip3 install gunicorn psycopg2-binary

Gunicorn ‘Green Unicorn’ is a Python WSGI HTTP Server for UNIX. It’s a pre-fork worker model ported from Ruby’s Unicorn project. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy. Gunicorn documentation.

 

Project configuration.

Open settings.py file:

(env) $ nano myproject/myproject/settings.py

 

Edit allowed hosts:

ALLOWED_HOSTS = ['localhost',]

 

Set DEBUG value to False:

DEBUG = False

 

Set database configuration:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydatabase',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'my_secure_password',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

Save and exit settings file.

 

Migrate the initial database schema to PostgreSQL database:

(env) $ python3 manage.py makemigrations
(env) $ python3 manage.py migrate

 

Create administrative user for project:

(env) $ python3 manage.py createsuperuser

 

 Collect static files:

(env) $ python3 manage.py collectstatic

 

Run server for test:

(env) $ python3 manage.py runserver 0.0.0.0:8000

 

Open in browser your project:

http://server_domain_or_IP:8000

 

Check with Gunicorn:

(env) $ cd project
(env) $ gunicorn --bind 0.0.0.0:8000 myproject.wsgi

 

Deactivate virtual environment:

(env) $ deactivate

 

Create systemd socket and service files for Gunicorn.

Create systemd socket file for Gunicorn:

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

 

And enter following configuration:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

 

Create systemd service file for Gunicorn:

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

 

 Enter this configuration:

[Unit]
Description=Your application's description
Requires=gunicorn.socket
After=network.target

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

[Install]
WantedBy=multi-user.target

 

Start Gunicorn socket:

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

 

Check Gunicorn socket file:

sudo systemctl status gunicorn.socket 

 

Check for the existence of gunicorn.sock file within the /run directory:

file /run/gunicorn.sock

 

Output:

/run/gunicorn.sock: socket

 

Test socket activation:

sudo systemctl status gunicorn

 

Using curl:

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

 

Reload daemon and restart Gunicorn:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn
sudo systemctl enable gunicorn

 

Configure Nginx with proxy_pass.

Create new server block:

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

 

Insert configuration in file:

server {
    listen 80;
    server_name server_domain_or_IP;

    client_max_body_size 20M;

    location = /favicon.ico { access_log off; log_not_found off; }


    location /static/ {
        root /home/user/myproject;
    }

    location /media {
        alias /home/user/myproject/media;
    }

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

 

    location = /robots.txt {
        alias /home/user/myproject/robots.txt;
    }
}

 

Create symlink for new configuration:

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled 

 

Test Nginx configuration for syntax errors:

sudo nginx -t

 

Restart Nginx:

sudo systemctl restart nginx
sudo systemctl enable nginx

 

Open 80 (http) and 443 (https) ports for Nginx:

sudo ufw allow 'Nginx Full'

Top button