PageView

Subscribe to our YouTube Channel!
[Jul 12, 2021] New Video: How to Use Django Rest Framework Permissions (DRF Tutorial - Part 7)


How to Deploy a Django Application on RHEL 7

How to Deploy a Django Application on RHEL 7

In this tutorial, you will learn how to deploy a Django application with PostgreSQL, Nginx, Gunicorn on a Red Hat Enterprise Linux (RHEL) version 7.3. For testing purpose I’m using an Amazon EC2 instance running RHEL 7.3.

Recently I had to deploy an existing Django project running on Ubuntu 16.04 to a new environment, RHEL 7.3. It gave me some headache because I don’t have much server administration skills and I wasn’t familiar with Security Enhanced Linux (SELinux) distributions, so I thought about sharing the details of the deployment so it could help someone in the same position I was.

If you are just getting started with Django deployment, and doesn’t have a good reason to be using RHEL, I suggest you use Ubuntu instead. It requires less configuration, and the process is fairly easier than using RHEL. Perhaps you could check this past tutorial: How to Deploy a Django Application to Digital Ocean.

Anyway, for this tutorial I will deploy the following Django application: github.com/sibtc/urban-train. It is just an empty Django project to demonstrate the deployment process. So, every time you see urban-train, change it for your project name.


Initial Setup

First, let’s install all the needed resources and applications. Get started by installing git, gcc and python-virtualenv. Everything should be available in the yum repository.

sudo yum -y install git gcc python-virtualenv

Create a system user for the application:

sudo groupadd --system urbantrain
sudo useradd --system --gid urbantrain --shell /bin/bash --home /opt/urban-train urbantrain

Create the Django project home inside /opt:

sudo mkdir /opt/urban-train

Give the permissions to the urbantrain user:

sudo chown urbantrain:urbantrain /opt/urban-train

PostgreSQL Server

Now install PostgreSQL 9.6 server and development tools:

sudo yum -y install https://yum.postgresql.org/9.6/redhat/rhel-7-x86_64/pgdg-redhat96-9.6-3.noarch.rpm
sudo yum -y install postgresql96-server postgresql96-contrib postgresql96-devel

Initialize the database:

sudo /usr/pgsql-9.6/bin/postgresql96-setup initdb

Start and enable the PostgreSQL 9.6 service:

sudo systemctl start postgresql-9.6
sudo systemctl enable postgresql-9.6

Log in with the postgres user:

sudo su - postgres

Create a database user, set a password (save it for later) and create a database for the Bootcamp application:

createuser u_urban
psql -c "ALTER USER u_urban WITH PASSWORD '123';"
createdb --owner u_urban urban_prod

Now we have to update the authentication method of the database user in the file pg_hba.conf:

vi /var/lib/pgsql/9.6/data/pg_hba.conf

Go to the bottom of the file, find this snippet:

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            ident
# IPv6 local connections:
host    all             all             ::1/128                 ident
# Allow replication connections from localhost, by a user with the
# replication privilege.
#local   replication     postgres                                peer
#host    replication     postgres        127.0.0.1/32            ident
#host    replication     postgres        ::1/128                 ident

Change the method from ident to md5 on the IPv4 and IPv6 rows:

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5  # <- here
# IPv6 local connections:
host    all             all             ::1/128                 md5  # <- and here
# Allow replication connections from localhost, by a user with the
# replication privilege.
#local   replication     postgres                                peer
#host    replication     postgres        127.0.0.1/32            ident
#host    replication     postgres        ::1/128                 ident

Save the file and exit.

Now, log out from the postgres session:

exit

Restart the PostgreSQL 9.6 server:

sudo systemctl restart postgresql-9.6

Python Virtual Environment

First, log in with the urbantrain system user:

sudo su - urbantrain

Start a new python-virtualenv inside the /opt/urban-train directory:

virtualenv venv

Activate the python-virtualenv:

source venv/bin/activate

Create a directory named logs that will be used by Gunicorn and Nginx to write the logs:

mkdir logs

Clone your project’s repository inside the /opt/urban-train directory:

git clone git@github.com:sibtc/urban-train.git

Now we have to install the Python dependencies. But first, add the PostgreSQL to the path. The psycopg2 will need it to install:

export PATH=$PATH:/usr/pgsql-9.6/bin/

Upgrade the Python package manager:

pip install pip --upgrade

Install the dependencies (/opt/urban-train/urban-train/requirements.txt inside the repository):

pip install -r requirements.txt

Migrate the database:

python manage.py migrate

Collect the static assets (css, javascripts, images, etc.):

python manage.py collectstatic --noinput

Gunicorn

Still logged in with the urbantrain user, let’s create a gunicorn_start file to startup the application server.

vi /opt/urban-train/gunicorn_start

Use the structure below, change the paths, user/groups etc accordingly to your environment/project:

#!/bin/bash

NAME="urban_train"
DJANGODIR=/opt/urban-train/urban-train
USER=urban
GROUP=urban
WORKERS=3
BIND=unix:/opt/urban-train/run/gunicorn.sock
DJANGO_SETTINGS_MODULE=urban_train.settings
DJANGO_WSGI_MODULE=urban_train.wsgi
LOGLEVEL=error

cd $DJANGODIR
source venv/bin/activate

export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

exec venv/bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $WORKERS \
  --user=$USER \
  --group=$GROUP \
  --bind=$BIND \
  --log-level=$LOGLEVEL \
  --log-file=-

Make the gunicorn_start file executable:

chmod u+x gunicorn_start

Create a directory named run, for the unix socket file:

mkdir run

Gunicorn Systemd Service

Now let’s create a systemd service file for gunicorn server to manage

First, exit the urbantrain user. Create the following systemd service file:

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

Insert the following in the file:

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

[Service]
User=urbantrain
Group=urbantrain
WorkingDirectory=/opt/urban-train
ExecStart=/opt/urban-train/gunicorn_start

[Install]
WantedBy=multi-user.target

Start the gunicorn systemd service we created and enable it so that it starts at boot:

sudo systemctl start gunicorn
sudo systemctl enable gunicorn

Nginx

The application must be served behind a proxy server. First, create a yum repo file:

sudo vi /etc/yum.repos.d/nginx.repo

Add repository information:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/7/$basearch/
gpgcheck=0
enabled=1

Save and exit.

Now install nginx:

sudo yum -y install nginx

Because of the security policies of the SELinux, we need to manually add the httpd_t to the list of permissive domains, run this command:

sudo semanage permissive -a httpd_t

Now let’s create a .conf file for the project. Go to the conf.d directory:

cd /etc/nginx/conf.d/

Remove the default.conf file, and create a new one for our project:

sudo rm default.conf
sudo vi urban-train.conf

Inside of the urban-train.conf file, insert the new server block:

upstream app_server {
    server unix:/opt/urban-train/run/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;
    server_name IP_ADDRESS_OR_DOMAIN_NAME;  # <- insert here the ip address/domain name

    keepalive_timeout 5;
    client_max_body_size 4G;

    access_log /opt/urban-train/logs/nginx-access.log;
    error_log /opt/urban-train/logs/nginx-error.log;

    location /static/ {
        alias /opt/urban-train/static/;
    }

    location /media/ {
        alias /opt/urban-train/media/;
    }

    location / {
        try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://app_server;
    }
}

Test the nginx.conf:

sudo nginx -t

Start the nginx service and enable it so that it starts at boot:

sudo systemctl start nginx
sudo systemctl enable nginx

Final Remarks

Everything should be working now. Do a final test, reboot the server and check if everything starts up normally:

sudo reboot

Some things that may cause trouble: