07 March 2012 ~ 12 Comments

Django Settings for Production and Development: Best Practices

When you’re just starting out with Django, it can be overwhelming to see there’s no standard approach to deal with settings. However, there are a few simple best practices that work when you start needing more than the basic settings file.

What’s the problem? Django stores all the settings in a project-wide settings.py file. All is fine until the moment you need different settings for different environments(such as a production environment and a development environment to start with).

Of the necessity to have different settings files

Your development settings should be different from your production settings. Why?

  • you should protect sensitive things like database passwords, api secrets, private keys in a separate file
  • the behavior of production code is not suited for development : for example you don’t want to send an email when you’re developing new features

Here are a few things that may change from one environment to another:

  • database details: database name, user name and password
  • api keys
  • your own private keys for encrypting data
  • debug variables (DEBUG and TEMPLATE_DEBUG)
  • path to different tools needed by your Django applications
  • and any other flags needed to make your application work differently in development and in production

We’re going to compare three different ways to organize your settings when having one setting file doesn’t cut it anymore.

First solution: local settings

This solution relies on having one settings.py file with common settings and a local_settings file where you define environment-specific settings.

Let’s see:

### settings.py file
### settings that are not environment dependent
try:
    from local_settings import *
except ImportError:
    pass
### local_settings.py
### environment-specific settings
### example with a development environment
DEBUG = True
DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'django',
    'USER': 'django',
    'PASSWORD': '1234',
    'HOST': '',
    'PORT': '',
}
  • Advantages: simple if you only need a development and a production environment (no staging) – the local_settings.py should stay out of source control and you need to have a separate one for development and production.
  • Disadvantages: it limits what you can do with settings such as modify common settings in the local_settings for example. It can work for the most simple case though.

Second solution: environment-based settings

This is one from Ches Martin. It relies on having an environment variable pointing to the right python module. It has the advantage of being explicit, so you can name your specific settings files explicity (production file being production.py for example).

[myapp]$ls settings
__init__.py
defaults.py
dev.py
staging.py
production.py

What happens when we do import settings? Settings is in this case a package (a package in Python being a directory with an __init__.py file inside), so when the interpreter loads the package it executes __init__.py.

By default, let’s make it work in development environment.

### __init.py__
from dev import *
### default.py__
### sensible choices for default settings
### dev.py
from defaults import *
DEBUG = True
### other development-specific stuff
### production.py
from defaults import *
DEBUG = False
### other production-specific stuff

How to use it in production and staging environments?

The trick is that the settings module location can be overriden by settings an environment Django variable. So we need to override that variable before starting our webserver. For example if you use Apache as your Django web server, modify your Apache configuration file with:

SetEnv DJANGO_SETTINGS_MODULE myapp.settings.production

Third solution: system-wide settings

### settings.py
import os
ENVIRONMENT_SETTING_FILE = '/etc/django.myproject.settings'
### this will load all environment file settings in here
execfile(ENVIRONMENT_SETTING_FILE)
### all common settings
### ...

We can see one problem of doing it this way is that we cannot modify variables defined in the common settings.py file in the environment-specific files.

On the other hand, it simplifies the management of environment-specific settings files. Create a file for development, staging and production, secure it with some tight permissions, and forget about it.

To conclude

There are other ways to manage your settings, so you can experiment a little bit. Check the list of resources to know more about managing your settings. And drop a comment here if you want to share the way you’re doing it!

Resources:

Congrats! You made it until the end. You can follow me on Twitter.

12 Responses to “Django Settings for Production and Development: Best Practices”

  1. Pambo 7 March 2012 at 1:19 pm Permalink

    Hey Tommy,

    An alternative way, possibly more centralized, in the sense that you don’t need to maintain separate files, is the following:
    - Keep all configuration parameters in the buildout script.
    - Keep the settings.py as a template file, that sets its template parameters according to the values of the config parameters in the buildout.
    - On build, parse the template using recipe collective.recipe.template, and output it in your project folder.

    Voila!

    No separate settings files to maintain. Just a centralized configuration file, which builds the settings file. You only tweak the template, and the buildout script, and the rest appears automagically!

  2. Luciano Pacheco 7 March 2012 at 2:56 pm Permalink

    Full local_settings:

    imort os
    PROJECT_DIR = os.path.dirname(os.path.dirname(__file__))
    try:
    execfile(os.path.join(PROJECT_DIR, “local_settings.py”), globals(), locals())
    except IOError:
    pass

    So, in your local_settings.py you can change any var. :-)

  3. tommy 7 March 2012 at 5:18 pm Permalink

    @Pambo, that is, if you use buildout ;-) Never used it yet. Thanks for the alternative dude :-)

  4. tommy 7 March 2012 at 5:20 pm Permalink

    that’s a good one, thanks Luciano!

  5. fourk 7 March 2012 at 8:58 pm Permalink

    Another pattern that can be useful with the first approach when you want to modify existing settings instead of using either/or default/overridden value for a given setting, adding a bit of flexibility to that approach.

    from environment import *
    if ‘EXTRA_INSTALLED_APPS’ in locals():
    INSTALLED_APPS += EXTRA_INSTALLED_APPS

  6. Alex 7 March 2012 at 10:26 pm Permalink

    I like to invert your option #1. I have a settings_common.py that has a bunch of shared configuration, and some sane defaults. I also have a settings.template file that is in source control and imports settings_common. The advantage is that you can then modify settings bit by bit, such as adding additional apps to particular deployments. Here is a gist that shows what I mean: https://gist.github.com/1996312

  7. Pambo 8 March 2012 at 10:50 am Permalink

    @tommy: How do you handle deployments? Fabric?

  8. tommy 8 March 2012 at 2:30 pm Permalink

    @Pambo yup. Fabric. It’s pretty awesome actually, might write a blog post about it one of these days :-)

  9. Pambo 8 March 2012 at 3:58 pm Permalink

    @tommy: Can’t wait for it ;)

  10. Michael Korbakov 10 March 2012 at 1:26 am Permalink

    @Pambo: we tried approach with buildout variables and templated settings.py in our application and found that it’s not really convenient. It worked OK until we weren’t concerned about running full continuous delivery pipeline. However shortly after starting building such pipeline we found that templating makes very difficult to follow “Single Build Artifact” approach that’s, in my opinion, is really worth to have in your workflow.
    After some research and considerations we started to use plain key=value files for each environment (similar to option 2 in the article) and never looked back. Django settings.py loads and parses corresponding file, Jenkins CI can use it too and special wrapper for supervisord pushes all values from this file into environmental variables so they can be used from any other non-Python process that started by supervisord.
    Really happy with this setup so far.


Leave a Reply