PostgreSQL Django Nginx Gunicorn with VirtualenvWrapper on 16.04 LTS Ubuntu Server

Hello, and welcome!

If you’ve recently upgraded to Ubuntu 16.04 LTS, you probably are wondering if there are any revamps you might need to do. In my case, I had to.

Short story

I deploy my stuff entirely on DigitalOcean. Since 16.04 was recently released, I checked my DO control panel and realized I could move from the 14.04 to the 16.04. From the control panel, I follow the instructions and got the upgrade done.

However, the upgrade actually wasn’t done! The CP showed Linux kernel 4.4 on 16.04 Ubuntu, whereas doing a simple uname -rvs in my terminal on the server box printed that I am still on 14.04.

This came to light after I realized I couldn’t install LetsEncrypt. Issuing the command to install on 16.04 failed, as my box was still on 14.04.

Fast-forward, I manually changed my sources.list of apt-get ; I simply replaced all trusty to xenial in the file, after I made sure I had a backup. sudo apt-get update && sudo apt-get dist-upgrade was all left.

After about 10 minutes, everything came back to normal, except my Nginx had gone up to 1.1 and my PHP has shifted to version 7. All were exciting news, however, I needed to put a couple of changes in to get things going!

So Django’s Turn Came

In this piece, I wish to share with you how to get a quick turn around with setting up your PostgreSQL, Django 1.9, Nginx, Gunicorn with VirtualEnvWrapper under minutes, less than 10, actually!

Prepping Surgery Table

sudo apt-get install python-pip python-dev \
python3-pip python3-dev libpq-dev postgresql \
postgresql-contrib nginx

The above ensures you install the necessary packages, such as Python and Python3 and pip and pip3, along with Nginx and PostgreSQL. Oneliner, done! You might not need to install the python3 and pip3 if you’re not going to use any of them. It is up to you.

Our surgery table happens to be clean now, free of germs. Let’s bring the actual objects to operate on! In this case, the first in line:

PostgreSQL Setup

We’re using PostgreSQL as the database backend in our setup. PostgreSQL is a powerful relational database system, and perfectly marries Django in all of its glory. I personally have been using PostgreSQL, and the experience is solid. No wonder it is well and highly spoken of!

You probably have your PostgreSQL database setup already, since I assume you had everything running and only had to upgrade from 14.04 to 16.04. If that is what you did (like I did), you don’t have to set up anything anymore. Just jump to the Django part, and keep going.

Your databases, passwords etc are all available, and tying into your Django project should require details last used on 14.04.

If you are starting afresh with PostgreSQL setup, here we go:

sudo -u postgres psql

CREATE DATABASE newproject;

CREATE USER projectuser WITH PASSWORD 'password';

// django recommends values below set: 
// http://devdocs.io/django~1.9/ref/databases
// using the ALTER ROLE to get things done!

ALTER ROLE projectuser SET client_encoding TO 'utf8';
ALTER ROLE projectuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE projectuser SET timezone TO 'UTC';

GRANT ALL PRIVILEGES ON DATABASE newproject TO projectuser;
// exit
\q

The above are standard commands you probably have done already. Replace newproject and projectuser with actual names of your choice.

We entered the psql (PostgreSQL) shell and issued the commands to create the database, a new user and assign a password, set some settings on the created user which will be used for transactions. Anytime a transaction from this user goes to the DB, these settings will be attached.

Next, we assign the database to the user and exist.

Database setup was done!

Python Development Environment

If you’ve not heard of VirtualEnv, then please go ask Google for more details. In short, it allows you to run your Python projects within encapsulated environments, such that, what packages or dependencies you install in project A, doesn’t interfere with project B.

We’ll be going an extra step in using VirtualenvWrapper to make development slightly easier.

So from the official docs, we do this:

sudo pip install virtualenv virtualenvwrapper

After install, we now have our virtualenv and its wrapper ready. To start making use of the commands given us by virtualenvnwrapper, you could add this to end of the file, ~/.bashrc

export WORKON_HOME=/home/django/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

Then, on the terminal, enter:

source ~/.bashrc

To reload your bash, giving you access to all the new commands from virtualenvwrapper

Setup the environment for the project with virtualenvwrapper

mkvirtualenv myProject
workon myProject

to create a new virtual environment called myProject. Notice where the project is initialized. Mine is at `/home/khophi/.virtualenvs/myProject/bin/python`.

After entering the workon myProject, your terminal should change to something like this:

(myProject)user@host:~/

Then do the next:

(myProject) $ cd ~/
(myProject) $ install django gunicorn psycopg2
(myProject) $ django-admin.py startproject myApp
(myProject) $ cd myApp/

Into Django’s Realm

Now that we have our Django project started, in the name of myApp and we’re in the project’s root directory, let’s go ahead to make some changes to the settings file to point to our PostgreSQL database.

Using nano, open the Django settings file of the myApp project, like so: (myProject) myApp/ $ nano myApp/settings.py

Then, in there:

.....
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'newproject',
        'USER': 'projectuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}
....

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
.....

With that out of the way, we need to test if our database connection is working.

To serve static files in production, you need to have your STATIC_URL and STATIC_ROOT set properly. In the above, we’re saying, ‘Look into the static/ folder within the root of the current project’.

Thus, whenever you do, {% static 'my/image/is.jpg'%} you’re saying, mywebsite.com/static/my/image/is.jpg which will be intercepted by our Nginx which is configured to serve files coming, having a starting URL of /static/ with the contents from the os.path.join(BASE_DIR, 'static/'), which in our case, is ~/myProject/static/

Testing database connection:

(myProject) $ cd ~/myApp
(myProject) myApp $ ./manage.py makemigrations 
(myProject) myApp $ ./manage.py migrate
(myProject) myApp $ ./manage.py createsuperuser
(myProject) myApp $ ./manage.py collectstatic --no-input

The above are standard Django commands, you already know. Since we wish to use Nginx to serve the static files. So, let us ‘taste’ the fruits of our work now:

(myProject) myApp/ $ ./manage.py runserver 0.0.0.0:8000

Assuming you don’t have firewall blocking the outside world in accessing your site, (of which you can remove by doing sudo ufw allow 8000), then visit

yourIpAddress:8000 in your browser, and you should see a Django ‘it worked’ page like so:

Django “It Worked” page

If you see the above page, then you’re a happy man. However, not fully a happy man, as we need to test one more thing. We plan using Gunicorn (The Python WSGI HTTP Server for UNIX) to serve our Django (Python) project. Shall we test to see how that’ll be:

(myProject) $ cd ~/myApp
(myProject) myApp/ $ gunicorn --bind 0.0.0.0:8000 myApp.wsgi:application

You might get errors relating to ‘No WSGI module found’ or something similar. Kindly check that you’re pointing to the WSGI file and referencing the application function in it properly.

If you hit up the site in your browser, like we did earlier, this time, your site or admin page will look broken. Why? Gunicorn doesn’t know anything about where you’re storing your static files. That job will be done by Nginx, and we’ll get there soon.

[wp_ad_camp_1]

A broken layout page of Django admin, when served with Gunicorn is no sign of error, it simply means, you’re on the right track, just left with a couple of ‘Engines’ (Nginx – Happy to pronounce it the wrong way!).

In this case, since we are in our project root already (myApp/) and the WSGI file is in the myApp/myApp/folder, that is why we’re accessing via the myApp.wsgi:application

If Gunicorn is able to boot properly and serve the site properly without any errors, then our Gunicorn is fine and our project is good to go.

We don’t want to come here enter this Gunicorn command all the time to start our Django server. What if our server when we’re asleep? That’ll mean no one accesses our site until we wake up in the morning, finish brushing our teeth, saying hi to friends and neighbors, before fixing our damn broken site!

We don’t want that! Let’s automate with a systemd service, which will kick in the server whenever the server boots and the network are up!

SystemD Service for Gunicorn

This step is straightforward. Create a service for the specific Django project, sudo nano /etc/systemd/system/myapp.service .

In this myapp.service, add:

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

[Service]
User=user
Group=www-data
WorkingDirectory=/home/user/myApp
ExecStart=/home/user/.virtualenv/myProject/bin/gunicorn --workers 3 --bind unix:/home/user/myApp/myproject.sock myApp.wsgi:application

[Install]
WantedBy=multi-user.target

The above is the basic structure for a systemd service file.

Kick the above file into action by doing

sudo systemctl start gunicorn
sudo systemctl enable gunicorn

Done!

Engines Revving, Nginx!

Our last but also useful piece in this PostgreSQL-Django-Nginx-Gunicorn Euro Championship is the Nginx.

Our Nginx will serve as the front-facing server. Any incoming request from a user that matches a particular domain but doesn’t match any location block we specify in the Nginx server configuration will be pushed to Gunicorn, which is standing by to accept forwarded requests and respond accordingly.

Something like this will do. CTRL + X will save the file when done, after opening a new Nginx conf for our myApp project with this: sudo nano /etc/nginx/sites-available/myapp

server {
    listen    443 ssl http2;
    listen [::]:433 ssl http2;

    root /home/user/myApp; # this is important
    index index.html index.htm;

    server_name logecg.khophi.co;
    # maybe you have an ssl
    include /etc/nginx/ssl/globalssl.conf;

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

    location /static/ {
	alias /home/user/myApp/static/;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://unix:/home/user/myApp/myapp.sock;
    }
   
    #location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    #    expires 30d;
    #}

    #location /robots.txt {
    #    alias /home/user/myApp/static/robots.txt;
    #}

    #location /favicon.ico {
    #    alias /home/user/myApp/static/favicon.ico;
    #}

    # maybe gzip stuff here too
}

server {
        listen 80;
        server_name logecg.khophi.co;
        return 301 https://$server_name$request_uri;
}

The above is a snippet taken from the Nginx configuration for the logECG website. This might match your needs if you’re using SSL on your website, otherwise, you simply can comment out the last server block, then replace listen 443 ssl http2;in the first server blog, with listen 80;and you’re good to go.

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
sudo nginx -t
// if no errors, restart nginx
sudo service nginx restart
// then allow Nginx 
sudo ufw allow 'Nginx Full'

Visit server_name you specified, in the above, let us say, logecg.khophi.co, and you see your myApp project up and running with the “It Worked” page from above.

This has been a long guide, but I am sure you were able to follow, and rushing through this app should get you up and running less than 10 minutes.

Thanks for reading, and I’ll see you in the next one. Lemme know if you have any questions.

 

Exit mobile version