Creating A Production Ready Django App

This post will demonstrate a production tested and simplified configuration for Django framework. This is a two part blog post. In the first part we will mainly focus how to structure properly because with larger projects managing file will usually result in an unpleasant experience somewhere around your anal area. Furthermore this post will show you the best practices of keeping secrets such as passwords hidden from your CVS.

This tutorial will use git and virtual environment so make sure you have these programs installed.


Word test in this blog post is synonymous with word development. this means that files such as could also be called keep that in mind!

Initialize New Django Project

go to dir you want your django project installed:

cd ~/<your-projects-dir>

create a folder for our django repository:

mkdir <my-django-repo>
cd <my-django-repo>

create documentation entries:

mkdir docs

create and activate a virtual environment (with python3):

virtualenv --python="$(which python3)" env
source env/bin/activate

create a basic .gitignore file:

echo "*.pyc" >> .gitignore
echo "env/" >> .gitignore

install Django:

pip install django
pip install ConfigParser
pip install sphinx  # (optional, used for documentation)

start a new project: startproject <cool_project>

start a new app (optional):

cd <cool_project>
python startapp <sexy_app>
cd ../

create folders for further use:

mkdir .secrets
mkdir logs
mkdir static  # optional but I recommend creating one
mkdir sandbox # optional for experiments and other stuff

add non-tracked directories to .gitignore:

echo ".secrets/" >> .gitignore
echo "logs/" >> .gitignore
echo "static/" >> .gitignore
echo "sandbox/" >> .gitignore
echo '*.sqlite3' >> .gitignore

Some of you may scream that creating logs or secrets dir in repo directory is madness. But bear with me, in production environment this will not be a directory but rather a symlink to e.g.:

./logs -> /var/log/django/logs

This symlink method will allow us to use less configuration, because both dev and prod environments will write logs to the same directory. Additionally we will decouple our configuration from OS based paths such as: /var/log/django/logs since any absolute path is not system agnostic.

your project structure should look like this (tree depth=2):

├── .gitignore
├── .secrets
├── cool_project
│   ├── cool_project
│   └──
├── docs
├── env  # # this dir is irrelevant
├── logs
├── sandbox
└── static

Create Prod and Test Settings Files

cd <cool_project>
cd <cool_project>
mkdir conf
cd conf
printf "from .settings_main import *\n# Test Setting Overrides\n" >
printf "from .settings_main import *\n# Prod Setting Overrides\n" >

we just created a new python package for our settings in the snippet above. this package will contain two different config files. The reason why we did this is because we want to have settings inheritance[^1]. This means that we will define our core configuration in while minor overrides based on the environment(test, prod) will be done in their appropriate files and This makes our configuration structure more similar to one you would find in Nginx.
Nginx like configuration structure forces your settings to be DRY.

enable test settings for our project:

ln -s

Symlinks again! This symlink is the magic symlink that will allow us to quick swap our production and test settings even when developing. This is very useful, because it is very likely that you will need to run your Django project with production settings on your development machine. So if you ever need to use simply: ln -s ./

move settings file from its original position:

mv ../ ./

what we did here is we said goodbye to original path of the file this file was created when running startproject <cool-project>. As I already mentioned we will use it for settings inheritance[^1]

edit file so that it will point to our new symlink:

vim ../

# change this line from:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<cool_project>.settings")

# to:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<cool_project>.conf.settings")

edit file so that it will point to symlink:

vim ../../

# change this line from:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<cool_project>.settings")

# to:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<cool_project>.conf.settings")

Last two snippets needed changing because if you recall we created a new python module called conf this meant that module which holds Django project settings has changed so we needed to tell and to take our project settings from a new location

test your server:

cd ../..
source ../env/bin/activate && python runserver

If development server started without any issues GZ! You have a Django project skeleton. Follow chapters below as we add a special file used to keep our passwords away from CVS.

do your initial git commit:

cd ..
git init
git status
git add .gitignore
git add .
git commit -m "Initial Django Commit"

repository directory structure demo:

├── .git  # this dir is irrelevant
├── .gitignore
├── .secrets
├── .swp
├── cool_project
│   ├── cool_project
│      ├──
│      ├── conf
│         ├──
│         ├── ->
│         ├──
│         ├──
│         └──
│      ├── db.sqlite3
│      ├──
│      └──
│   └──
├── docs
├── env # this dir is irrelevant
├── logs
├── sandbox
└── static

Append the settings file

In this chapter we will add extra few essentials such as log file configuration, passwords, secrets and more. I recommend you to copy code found below. Lines you copy and paste will include comments make sure you read them.

create a secrets.ini file:

touch .secrets/secrets.ini

paste this to .secrets/secrets.ini:

secret_key = '<your_production_secret_key>'

secret_key = '<your_test_secret_key>'

secret_key = '...' : This is your production server secret key. It must not match one under the [test] section. Read more about it here

delete lines in


paste new lines in the same area


import io
import configparser

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
SECTION = 'test'  # when using production then SECTION = 'prod'

# we create a base directory 2 parent directories UP.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# we need to go one dir up because our settings file in one folder deeper than default
BASE_DIR = os.path.dirname(os.path.dirname(BASE_DIR))
SECRETS_DIR = os.path.join(BASE_DIR, '.secrets')
SECRETS_FILE_PATH = os.path.join(SECRETS_DIR, 'secrets.ini')
LOGS_DIR = os.path.join(BASE_DIR, 'logs')
LOG_MAIN = os.path.join(LOGS_DIR, 'django_main.log')

CONF = configparser.ConfigParser()
# this is a magic line, because based on what value SECTION= holds we will
# able to choose our settings in this case test OR prod

# we grab our `secret_key` from our `secrets.ini` file
SECRET_KEY = SECRETS['secret_key']

add basic logging to file:

    'version': 1,
    'disable_existing_loggers': False,

    # you can have as many formatter you want.  assign different formatters to
    # different handlers

    'formatters': {
        'simple': {
            'format': '%(asctime)s - %(levelname)s - %(message)s'
        'multi': {
            'format': '(%(threadName)-10s) %(asctime)s %(levelname)s: %(message)s'

    'handlers': {
        'applogfile': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': LOG_MAIN,
            'formatter': 'multi',
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'multi',
    # every django app you create will have to have their logger set. set them
    # here in the 'loggers' object. loggers['django'] is the name of our project
    # leave it as is.
    'loggers': {
        'django': {
            'handlers': ['applogfile', 'console'],
            'level': 'DEBUG',


test run your project again:

cd <place where is found>
source ../env/bin/activate
python runserver

add lines to

This is where the override magic kicks in. Rather than writing our whole configuration from scratch we simply override values we want to change.


print(" {} ".format("using PRODUCTION settings").center(80, '='))

DEBUG = False
SECTION = 'prod'
SECRETS = CONF[SECTION]  # uses section [prod] in secrets.ini


# overrides sqlite3 DB to a production ready postgress one
# DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
# DATABASES['default']['NAME'] = 'username'
# DATABASES['default']['USER'] = 'password'
# DATABASES['default']['HOST'] = ''
# DATABASES['default']['PORT'] = '5432'

test run you project using prod settings

For this we will need to use the symlink trickery again. We are not changing files or changing imported files we will only change where symlink is pointing in this instance ->

cd <place where is found>
cd <cool_project>
cd <cool_project>
cd conf
rm  # let's delete old symlink
ln -s ./
cd ../../
python runserver

if all went well you should see:


but since we do not care about our prod settings when developing let us revert back to

cd <cool_project>
cd conf
rm  # let us delete old symlink
ln -s ./
cd ../../
python runserver


When we ran python runserver for the first time a new db.sqlite3 file was created, this file was created prior to our configuration changes and it will not be used anymore so let's remove it.

rm cool_project/db.sqlite3

Now we can make our second git commit

cd ..
git status
git add .
git commit -m "our second django project commit"

Now you have a better base for your future Django Projects.

What Do I Now?

  1. Create a new Django app in your project: python <my-super-app>
  2. Write awesome code
  3. If something did not work or you are lazy just copy skeleton we did:
  4. Tune for a second part, next week, where I will be showing you how to deploy your project using fabric2 and invoke to a production environment.