How to set up Django application with Postgres, Nginx, and Gunicorn on Rocky Linux/Alma Linux 9

In this guide we will learn how to set up django application on a Rocky Linux 9 server. We will be setting up a PostgreSQL database instead of using the default SQLite database. We will configure the Gunicorn application server to interface with our applications. We will then set up Nginx to reverse proxy to Gunicorn, giving us access to its security and performance features to serve our apps.

Related content:

# Prerequisites

In order to follow along, you should have a Rocky Linux 9 server with root access or user with sudo access. We will be installing Django within a virtual environment. Installing Django into an environment specific to your project will allow your projects and their requirements to be handled separately.

Once we have our database and application up and running, we will install and configure the Gunicorn application server. This will serve as an interface to our application, translating client requests in HTTP to Python calls that our application can process. We will then set up Nginx in front of Gunicorn to take advantage of its high performance connection handling mechanisms and its easy-to-implement security features.

# Ensure that the system is up to date and enable epel repository

Before proceeding, ensure that you have up to date packages. Use this command to update the packages on your server

1
sudo dnf update

Next, let us enable the epel repository:

1
sudo dnf install epel-release

Configuring SELinux is out of the scope of this guide, if you want to disable, open the /etc/selinux/config file and set the SELINUX mod to disabled:

File /etc/selinux/config

1
2
3
4
5
6
# This file controls the state of SELinux on the system.</em>
# SELINUX= can take one of these three values:</em>
#       enforcing - SELinux security policy is enforced.</em>
#       permissive - SELinux prints warnings instead of enforcing.</em>
#       disabled - No SELinux policy is loaded.</em>
SELINUX=disabled

To disable without rebooting the system, use the setenforce tool as follows:

1
sudo setenforce 0

To view the current SELinux status and the SELinux policy that is being used on your system, use the sestatus command:

1
sestatus

# Install required packages and set up database

Next let us install the required packages. Since we will use virtualenv, install the python packages and dependencies:

1
sudo dnf install -y python3 python3-devel python3-pip gcc

Next let us install postgres. Checkout this comprehensive guide on setting up postgres - How to Install and Configure Postgres 14 on Rocky Linux 9.

Install the repository RPM using this command:

1
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm

Then install PostgreSQL 14 server and its dependencies:

1
sudo dnf install -y postgresql14 postgresql14-server postgresql14-contrib postgresql14-devel python3-psycopg2

# Set Up PostgreSQL for Django

Let us set up postgres for our application. First initialize the PostgreSQL database:

1
2
$ sudo /usr/pgsql-14/bin/postgresql-14-setup initdb
Initializing database ... OK

Start the postgres service with this command:

1
sudo systemctl start postgresql-14

Now connect to the postgresql so we can create the user

1
2
sudo su - postgres
psql

Then create a postgres user

1
2
3
create database djangoapp;
create user djangouser with encrypted password 'dbpassword';
grant all privileges on database djangoapp to djangouser;

# Create a Python Virtual Environment for your Project

We will be installing our Python requirements within a virtual environment for easier management.

To do this, we first need access to the virtualenv command. We can install this with pip:

1
sudo pip3 install virtualenv

With virtualenv installed, we can start forming our project. Create a directory where you wish to keep your project and move into the directory afterwards:

1
2
mkdir /opt/djangoapp
cd /opt/djangoapp

Within the project directory, create a Python virtual environment by typing:

1
virtualenv appenv

This will create a directory called appenv within your project directory. Inside, it will install a local version of Python and a local version of pip. We can use this to install and configure an isolated Python environment for our project.

Before we install our project’s Python requirements, we need to activate the virtual environment. You can do that by typing:

1
source appenv/bin/activate

With your virtual environment active, install Django, Gunicorn, and the psycopg2 PostgreSQL adaptor with the local instance of pip:

1
pip install django gunicorn psycopg2 psycopg2-binary

If you run into an issue installing the dependencies regarding pg_* commands, append this to path before doing pip install:

1
export PATH=$PATH:/usr/pgsql-14/bin

# Create and Configure a New Django Project

With our Python components installed, we can create the actual Django project files.

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 the dot at the end that tells Django to create the files in the current directory. This will create /opt/djangoapp/myapp:

1
django-admin startproject myapp .

The first thing we should do with our newly created project files is adjust the settings. Open the settings file in your text editor:

1
2
cd myapp
vim myapp/settings.py

Start by finding the section that configures database access. It will start with DATABASES. The configuration in the file is for a SQLite database. We already created a PostgreSQL database for our project, so we need to adjust the settings.

Change the settings with your PostgreSQL database information. We tell Django to use the psycopg2 adaptor we installed with pip. We need to give the database name, the database username, the database username’s password, and then specify that the database is located on the local computer. You can leave the PORT setting as an empty string. We are going to expect the settings from the env variables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': os.environ.get('DATABASE_DB'),
        'USER': os.environ.get('DATABASE_USER'),
        'PASSWORD': os.environ.get('DATABASE_PASSWORD'),
        'HOST': os.environ.get('DATABASE_HOST'),
        'PORT': os.environ.get('DATABASE_PORT'),
    },
}

Next, move down to the bottom of the file and add a setting indicating where the static files should be placed. This is necessary so that Nginx can handle requests for these items. The following line tells Django to place them in a directory called static in the base project directory:

1
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

Save and close the file when you are finished.

# Django initial set up

Now, we can migrate the initial database schema to our PostgreSQL database using the management script:

1
2
3
4
5
6
7
8
export DATABASE_DB=<db name>
export DATABASE_USER=<db user>
export DATABASE_PASSWORD=<db password>
export DATABASE_HOST=127.0.0.1
export DATABASE_PORT=5432

python manage.py makemigrations
python manage.py migrate

Create an administrative user for the project by typing:

1
python manage.py createsuperuser

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

We can collect all of the static content into the directory location we configured by typing:

1
python manage.py collectstatic

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

Finally, you can test your project by starting up the Django development server with this command:

1
./manage.py runserver 0.0.0.0:8000

In your web browser, visit your server’s domain name or IP address followed by :8000:

1
http://server_ip_or_domain:8000

You should see the 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 with the createsuperuser command.

# Testing Gunicorn’s Ability to Serve the Project

The last thing we want to do before leaving our virtual environment is test Gunicorn to make sure that it can serve the application. We can do this easily by typing:

1
gunicorn --bind 0.0.0.0:8000 myapp.wsgi:application

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. Note that the admin interface will not have any of the styling applied since Gunicorn does not know about the static content responsible for this.

We passed Gunicorn a module by specifying the relative directory path to Django’s wsgi.py file, which is the entry point to our application, using Python’s module syntax. Inside of this file, a function called application is defined, which is used to communicate with the application.

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

We’re now finished configuring our Django application. We can back out of our virtual environment by typing:

1
deactivate

# Create a Systemd Service File

We have tested that Gunicorn can interact with our Django application, but we should implement a more robust way of starting and stopping the application server. To accomplish this, we’ll make a Systemd service file.

Create and open a Systemd service file for Gunicorn with sudo privileges in your text editor:

1
sudo vim /etc/systemd/system/myapp.service

Add these content to the file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[Unit]
Description=MyApp service
Wants=network-online.target
After=network-online.target

[Service]
User=rocky
Group=nginx
Type=simple
PIDFile=/opt/djangoapp/app.pid

Environment=ENV=live
Environment=DEBUG=1
Environment=ALLOWED_HOSTS=*
Environment=SECRET_KEY='django-secure-ATk95j*F!y2JM^5w*S2EG7uYpKUVL&#GsSa&xaFqHMgDzL5kUTj'
Environment=APP_PATH=/opt/djangoapp/myapp
Environment=DATABASE_DB=citizix
Environment=DATABASE_USER=citizix
Environment=DATABASE_PASSWORD=citizix@_24R
Environment=DATABASE_HOST=127.0.0.1
Environment=DATABASE_PORT=5432

WorkingDirectory=/opt/djangoapp/myapp
ExecStart=/opt/djangoapp/appenv/bin/gunicorn \
    -t 3000 \
    --bind unix:/opt/djangoapp/app.sock \
    --pid /opt/djangoapp/app.pid \
    --reload \
    myapp.wsgi:application -w 2

[Install]
WantedBy=multi-user.target

With that, our Systemd service file is complete. Save and close it now.

We can now start the Gunicorn service we created and enable it so that it starts at boot:

1
2
sudo systemctl start myapp
sudo systemctl enable myapp

# Configure Nginx to Proxy Pass to Gunicorn

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

Create a new virtualhost for our application:

1
sudo vim /etc/nginx/conf.d/myapp.conf

Then add these content to the file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
server {
    listen 80;
    server_tokens off;
    client_max_body_size 100M;
    server_name myapp.citizix.com;

    ignore_invalid_headers off;

    if ($host !~* ^(myapp.citizix.com)$ ) {
        return 444;
    }

    location = /favicon.ico  {
        rewrite "/favicon.ico" /static/img/favicon.ico;
        access_log off; log_not_found off;
    }

    location /static/ {
        root /opt/djangoapp;
    }

    location /media/ {
        root /opt/djangoapp;
    }

    location / {
        send_timeout            600;
        proxy_read_timeout      600;
        proxy_send_timeout      600;
        proxy_connect_timeout   600;
        proxy_redirect          off;
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Host $server_name;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_pass              http://unix:/opt/djangoapp/app.sock;
    }
}

Update the domain name from myapp.citizix.com to your domain and create a DNS record for that to point to the server.

# Adjust Group Membership and Permissions

The nginx user must have access to our application directory so that it can serve static files, access the socket files, etc. We will add the nginx user to our user’s group so that we can then open up the minimum permissions necessary to get this to function.

Add the nginx user to your group with the following command. Substitute your own username for the user in the command:

1
sudo usermod -a -G user nginx

Now, we can give our user group execute permissions on our directory. This will allow the Nginx process to enter and access content within:

1
chmod 710 /opt/djangoapp

With the permissions set up, we can test our Nginx configuration file for syntax errors:

1
sudo nginx -t

If no errors are present, restart the Nginx service by typing:

1
sudo systemctl start nginx

Tell the init system to start the Nginx server at boot by typing:

1
sudo systemctl enable nginx

You should now have access to your Django application in your browser over your server’s domain name or IP address without specifying a port.

# Conclusion

In this guide, we managed to set up django to run on Rocky Linux 9 instance. We started by installing the required packages and setting up postgres then created a virtual env that we used to isolate our app dependencies.

We also learnt how to use gunicorn to serve our django app and nginx to proxy requests to the app.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy