Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Lapicher/c6d686b121a4a71eb5a024eb49d6a72f to your computer and use it in GitHub Desktop.
Save Lapicher/c6d686b121a4a71eb5a024eb49d6a72f to your computer and use it in GitHub Desktop.
How to deploy a Django app with PostgreSQL + Gunicorn + Nginx using pyenv on Ubuntu Server 16.04

How to deploy a Django app with PostgreSQL + Gunicorn + Nginx using pyenv on Ubuntu Server 16.04

This guide shows how to setup a production environment for a Django application using PostgreSQL as database, Gunicorn as application server and Nginx as http server using Ubuntu Server 14.04 as Operative System.

Install PosgreSQL, Nginx, Git and Circus

Install PostgreSQL and Nginx using:

sudo apt-get install -y postgresql-9.5 postgresql-contrib-9.5 postgresql-server-dev-9.5 nginx git circus make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils python-setuptools  

Creating a user to run the application

For security reasons, it's a good practice to run web applications as a user with no root privileges so that, if the web applications allows some code execution due to a bug, the code never will adquire root privileges to hack the server.

Create the user:

sudo adduser <app_name>

Lock user account to deny users login via username and password to the server:

sudo passwd -l <app_name>

Add the nginx user (usually www-data) to the <app_name> group to have read privileges:

sudo adduser www-data <app_name>

Creating database and allow app user to the database

We have to create a database to store all application info and allow the <app_name> user to acces that database.

Creating database user:

sudo -u postgres createuser <app_name>

Creating database a giving <app_name> access to it:

sudo -u postgres createdb <app_name> -O <app_name>

Becoming <app_name> user

We have to clone the application in the <app_name> user home as the home user.

Due to we locked the <app_name> user, we cannot login to the server with that username.

Instead, we're going to become the <app_name> user from our root user by using:

sudo -u <app_name> -i

Now, we are logged as the <app_name> user and in the <app_name> user home. We can check it with pwd:

pwd
/home/<app_name>

Install pyenv and pyenv-virtualenv

pyenv allow us to install any Python's version in our user account. We are going to user pyenv to create a virtualenvironment with the Python's version we need.

To install pyenv and pyenv-virtualenv we need to checkout the project from github:

git clone https://github.com/yyuu/pyenv.git ~/.pyenv
git clone https://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv

After that, we need to update our .bash_profile to make pyenv work properly:

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile

Now we need to logout and login again as <app_name> user_

logout
sudo -u <app_name> -i

Installing our Python version and creating a virtualenv

Installing Python's version needed

To install the Python's version needed to run our app we can use pyenv:

pyenv install <python_version>

Example:

pyenv install 3.5.2

Note that the Python's version will be installed in: /home/<app_name>/.pyenv/versions/<python_version>

Creating a virtualenv using the Python's version installed

Now we can use 'pyenv-virtualenv' to create a virtualenv based on the installed Python's version:

pyenv virtualenv <python_version> <virtualenv_name>

Example:

pyenv virtualenv 3.5.2 env

Note that the virtualenv will be created in: /home/<app_name>/.pyenv/versions/<virtualenv_name>

App scaffolding and setup

Clonning the app

Now we can clone our Django project using git in a folder named app (this name is optional, obviously):

git clone <repo_url> app

Logs folder

We will create a folder called logs to store all logs generated by the application:

mkdir logs

Production settings

As a good practice, we must use a different settings for development and production because we must not store production data in the repo.

So that, we will create a settings_production.py to run the application in production:

nano app/<app_name>/settings_production.py

And we will all the settings needed for production but using environment variables (set by a root-privileged user):

import os
from settings import *

DEBUG = False
TEMPLATE_DEBUG = False

ALLOWED_HOSTS = ['*']

SECRET_KEY = os.environ.get('SECRET_KEY', SECRET_KEY)

ADMINS = ((
	os.environ.get('ADMIN_EMAIL_NAME', ''), 
	os.environ.get('ADMIN_EMAIL_ADDRESS', '')
),)

DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.postgresql_psycopg2',
       'NAME': os.environ.get('DB_NAME', ''),
       'USER': os.environ.get('DB_USER', '')
   }
}

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

MEDIA_ROOT = os.path.join(BASE_DIR, os.environ.get('MEDIA_ROOT', "media/"))
MEDIA_URL = os.environ.get('MEDIA_URL', "/media/")

Installing project's dependencies and gunicorn in virtualenv

Now we have to activate the virtualenv:

pyenv activate env

Befor install any package, we should update pip:

pip install --upgrade pip

Now we can install project dependencies:

pip install -r app/requirements.txt

And gunicorn:

pip install gunicorn

Aplying migrations, collecting static files and create super user

Once we have all dependencies installed, we have to apply Django migrations to our database and collect static files to be served from Nginx as static content.

But before that, we have to export the environment variables related to the database in order to be loaded from the settings_production file:

export SECRET_KEY=<secret_key>
export DB_NAME=<app_name>
export DB_USER=<app_name>
export DJANGO_SETTINGS_MODULE=<app_name>.settings_production

Now, access to app folder:

cd app

Apply migrations:

python manage.py migrate

Collect static files:

python manage.py collectstatic --noinput

Create super user:

python manage.py createsuperuser

Deactivating virtualenv and logout as app user

Now we are done with all the stuff as <app_name> user, so that we have to deactivate virtualenv with:

pyenv deactivate

and logout to become a root-privileged user:

logout

Setting up circus to run gunicorn

Now we have to install circus, a process manager written in Django that we will use to run gunicorn as a service.

App gunicorn settings

Let's create a file in /etc/circus/conf.d called <app_name>.ini to setup all the gunicorn running configuration:

sudo nano /etc/circus/conf.d/<app_name>.ini

The file must have this settings but replacing <variables> for their values:

[watcher:<app_name>]
working_dir = /home/<app_name>/app
cmd = gunicorn
args = -w 1 -t 180 --pythonpath=. -b unix:/home/<app_name>/app.sock <app_name>.wsgi
uid = <app_name>
numprocesses = 1
autostart = true
send_hup = true
stdout_stream.class = FileStream
stdout_stream.filename = /home/<app_name>/logs/gunicorn.stdout.log
stdout_stream.max_bytes = 10485760
stdout_stream.backup_count = 4
stderr_stream.class = FileStream
stderr_stream.filename = /home/<app_name>/logs/gunicorn.stderr.log
stderr_stream.max_bytes = 10485760
stderr_stream.backup_count = 4

[env:<app_name>]
PATH = /home/<app_name>/.pyenv/versions/env/bin:$PATH
TERM = rxvt-256color
SHELL = /bin/bash
USER = <app_name>
LANG = en_US.UTF-8
HOME = /home/<app_name>/app
PYTHONPATH = /home/<app_name>/.pyenv/versions/env/lib/python<PYTHON_VERSION_WITHOUT_LAST_NUMBER>/site-packages
DJANGO_SETTINGS_MODULE = <app_name>.settings_production
SECRET_KEY = <secret_key>
DB_NAME = <app_name>
DB_USER = <app_name>

Now, we only have to run circus:

sudo service circus start

Nginx setup

Ok, let's finish by setting up nginx to catch the traffic for a given domain and redirect it to gunicorn to serve dynamic content.

Let's create a file in /etc/nginx/sites-available called <app_name>:

sudo nano /etc/nginx/sites-available/<app_name>`

The file must have this settings but replacing <variables> for their values:

server {
	listen 80;
	server_name <your_domain>;

	access_log /home/<app_name>/logs/nginx-access.log;
	error_log /home/<app_name>/logs/nginx-error.log;

	root /home/<app_name>/app/;

	client_max_body_size 10M;

	location /static {
		alias /home/<app_name>/app/static;
	}

    location /media {
        alias /home/<app_name>/app/media;
    }

	location / {
		include proxy_params;
		proxy_pass http://unix:/home/<app_name>/app.sock;
	}
}

Now we have to make a symlink from the sites-available folder to sites-enabled folder:

sudo ln -s /etc/nginx/sites-available/<app_name> /etc/nginx/sites-enabled/<app_name>

And now, test the config with:

sudo nginx -t

You should receive:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Ok, let's gonna restart nginx:

sudo service nginx restart

Here we go!

Just open http://<your_domain>/admin/ in your browser!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment