One Django settings file to rule them all

In this article you’ll learn how to create one Django settings.py file that you can use everywhere and that you don’t have to touch across runtime environments. No matter if you are targeting production, staging, development, Docker deployments or whatever: configuring your apps won’t be a factor anymore. Let’s start!

Popular configuration approaches

Let's quickly take a look at two very popular approaches at first.

From what I've seen the most popular approach is using one settings file for each runtime environment: development.py, staging.py, production.py...

Not a bad thing to do at all. However you get a lot of duplicated code in your configuration files, which requires keeping all of them in sync all the time.

Avoiding configuration code duplication

A solution for avoiding duplication is to use a base settings file that contains defaults for most options, and then including everything in more specific files:

Config files are painful in containerized environments

The problem with using configuration files is that you have to create or somehow deliver them. In containerized environments (like Docker) that can be a real pain, because those files have to be transferred to where your app runs.

Add a cluster/Swarm to the equation and the fun level increases exponentially.

Docker, for example, works best when you have very generic images with a low configuration effort. It's much easier to just define a set of environment variables than copying files around.

Environment variables

In production environments it's very popular to configure apps by utilizing environment variables. With Docker, you can set them when running a container:

docker run -e SECRET_KEY=supersecret my_app

The result of moving the configuration part to environment variables is a very general, generic and thin Django settings.py file. One that we write once and use everywhere.

Making the new settings file

Let's finally create a settings.py (explanations follow below):

  1. The simplest form: Required settings. They are directly loaded from environ. In case a setting is missing, an exception will crash the app, signalling the user that he has to define it.
  2. Settings with a useful default value. I tend to choose defaults that rather match a production system than a development one. Main reason being not exposing critical internal details if I forget to set a configuration value.
  3. Some options require more complex data structures, such as lists or dictionaries. That's no real issue though, because the settings file is pure Python code: All batteries included. An easy solution is expecting the input to be in a specific format and transforming it into what we need ("A,B,C" becomes ["A", "B", "C"]).
  4. This is by far the most complex configuration variable in most projects: The database config. For maximum flexibility and minimum code size I choose to parse a JSON string from the environment variable. Super easy by using the json Python package which comes with every Python distribution.
  5. In some cases you might have to extend core settings that are normally not part of typical configurations, especially when debugging or developing. For example lots of projects use the 3rd party package "Django debug toolbar", so there should be a way to include apps without changing the settings file itself.

Prefixing variables

I'd like to note that I intentionally prefix every environment variable with APP_. The reason is avoiding name clashes with other environment variables. Of course you should replace APP by your app's name.

Setting environment variables during development

So far, so good. There's just one step missing: How do you set all your environment variables during development?

That's really simple: You set variables when activating your virtual environment. Since you are a sane and serious Python developer, I'm very sure that you use that, right? If not I won't tell anyone, but please use it in the future.

Just open your bin/activate file that you source for activating the virtual environment and add something like this to the end of it:

Now everytime you activate the environment (source virtenv/bin/activate), all configuration variables will be set and available to Django.

That's it, I hope you'll enjoy configuring your apps more in the future. ;-) See ya!


This article was published on June 30th, 2017 by stschindler and is tagged with