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-virtualenvCreate a system user for the application:
sudo groupadd --system urbantrain
sudo useradd --system --gid urbantrain --shell /bin/bash --home /opt/urban-train urbantrainCreate the Django project home inside /opt:
sudo mkdir /opt/urban-trainGive the permissions to the urbantrain user:
sudo chown urbantrain:urbantrain /opt/urban-trainPostgreSQL 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-develInitialize the database:
sudo /usr/pgsql-9.6/bin/postgresql96-setup initdbStart and enable the PostgreSQL 9.6 service:
sudo systemctl start postgresql-9.6
sudo systemctl enable postgresql-9.6Log in with the postgres user:
sudo su - postgresCreate 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_prodNow 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.confGo 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 identChange 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 identSave the file and exit.
Now, log out from the postgres session:
exitRestart the PostgreSQL 9.6 server:
sudo systemctl restart postgresql-9.6Python Virtual Environment
First, log in with the urbantrain system user:
sudo su - urbantrainStart a new python-virtualenv inside the /opt/urban-train directory:
virtualenv venvActivate the python-virtualenv:
source venv/bin/activateCreate a directory named logs that will be used by Gunicorn and Nginx to write the logs:
mkdir logsClone your project’s repository inside the /opt/urban-train directory:
git clone git@github.com:sibtc/urban-train.gitNow 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 --upgradeInstall the dependencies (/opt/urban-train/urban-train/requirements.txt inside the repository):
pip install -r requirements.txtMigrate the database:
python manage.py migrateCollect the static assets (css, javascripts, images, etc.):
python manage.py collectstatic --noinputGunicorn
Still logged in with the urbantrain user, let’s create a gunicorn_start file to startup the application server.
vi /opt/urban-train/gunicorn_startUse 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_startCreate a directory named run, for the unix socket file:
mkdir runGunicorn 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.serviceInsert 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.targetStart the gunicorn systemd service we created and enable it so that it starts at boot:
sudo systemctl start gunicorn
sudo systemctl enable gunicornNginx
The application must be served behind a proxy server. First, create a yum repo file:
sudo vi /etc/yum.repos.d/nginx.repoAdd repository information:
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/7/$basearch/
gpgcheck=0
enabled=1Save and exit.
Now install nginx:
sudo yum -y install nginxBecause 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_tNow 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.confInside 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 -tStart the nginx service and enable it so that it starts at boot:
sudo systemctl start nginx
sudo systemctl enable nginxFinal Remarks
Everything should be working now. Do a final test, reboot the server and check if everything starts up normally:
sudo rebootSome 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
A Complete Beginner's Guide to Django - Part 7
How to Deploy a Django Application to Digital Ocean
How to Deploy Django Applications on Heroku
How to Extend Django User Model
How to Setup a SSL Certificate on Nginx for a Django Application