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 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
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>
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>
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>
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
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>
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>
Now we can clone our Django project using git
in a folder named app
(this name is optional, obviously):
git clone <repo_url> app
We will create a folder called logs
to store all logs generated by the application:
mkdir logs
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/")
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
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
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
Now we have to install circus, a process manager written in Django that we will use to run gunicorn as a service.
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
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
Just open http://<your_domain>/admin/
in your browser!