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:
- SELinux permission issues, read more here: SELinux Changes when Upgrading to RHEL 6.6 / CentOS 6.6
- The nginx user does not have permission to project folders
- PostgreSQL authentication method