Django Configuration using Environment Variables

Created: 27.06.2022 | Last edited: 18.07.2022

Motivation

We want to set some of our django settings using environment variables. We might do so for ease of use in different environments. For example if you have a simple project and don't want to run separate settings files but still need to set DEBUG=True or DEBUG=False depending if your app runs in development or in a production environment. Another, more common, use case might be security concerns. Probably you don't want to store your database credentials directly in the settings file and push all of them into your repository. Also there is this little thing called "SECRET_KEY". Every wondered what this will do for you? It will protect your and your users data, like serializd json, or password reset requests, by cryptographically signing them and generating unique salt for hash functions. Or to simplify: it is used to secure sessions, data, and passwords across all apps that come with django (like the django admin) as well as all apps who might access such features. Pretty important stuff here. If you have already pushed your settings file with the secret key included to a repository and what to chance if due to security concers, I wrote a quick article with just one line of code to get you a new one :)

Method 1: classic environment variables

Reading environment variables using python

An easy native way of doing things is to write a function which can like right in our settings file that will read environment variables from the system like so:

import os

def get_environment_variable(setting):
    return os.environ[setting]

Next we want to import a proper django exception. If a setting we like to read from our environment variables isn's set properly as an environment variable, we still want it to throw an ImproperlyConfigured exception to know that the error that occured is actualy a configuration error:

import os
from django.core.exceptions import ImproperlyConfigured

def get_environment_variable(setting):
    try:
        return os.environ[setting]
    except KeyError:
        raise ImproperlyConfigured(f'{setting} environment variable not set.')

Set variables using scripts

Now we need to create scripts to run for our development and production environment (or any environment you need) which set all variables we need as it would be pretty annoying and prone to error to write all lines by hand on every machine.

So we can create two scripts, e.g. set_env_vars_dev.sh, like so:

export DJANGO_SECRET_KEY='...'
export DATABASE_NAME='...'
export DATABASE_USER='...'
export DATABASE_PASSWORD='...'

export CUSTOM_ENVIRON_CHECK='Environment Variables set successfully.'
echo "$CUSTOM_ENVIRON_CHECK"

I included a quick print to the terminal in the end as I'd like to see if the script ran properly before even running in an ImproperlyConfigured exception we created earlier.

Now in our settings code we can simply replace the information we want to store in environment variables by using our new functions with the keys from our script:

SECRET_KEY = get_environment_variable('SECRET_KEY')

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': get_environment_variable('DATABASE_NAME'),
        'USER': get_environment_variable('DATABASE_USER'),
        'PASSWORD': get_environment_variable('DATABASE_PASSWORD'),
        'HOST': '...',
    }
}

That's it. Now, at least if you don't push the scripts as well, you can push your code to GitHub or whatever without worrying about other dev's (or in case of OpenSource Code: everyone!) getting access to your secrets and credentials.

Method 2: django-environ

Why would you need an alternative method in the first place? Well, I don't know. First method works well. However...maybe you need your app to run on an unix environment and a windows environment and don't want to maintain several scripts to setup environment variables. Maybe you use another way of storing secrets and want a single "secrets file" including all your credentials to protect. In this case django-environ might be the package you need.

Setup environ

After running pip install django-environ we can add the following code to the beginning of our settings file:

import environ

# initialise environ
env = environ.Env()

# read environ variables
environ.Env.read_env()

But what "environ variables" get read here?

Create environ variables

For this we need to create a .env file in the root directory of our django site. Just create a plain text file like the following that you can rename to .env :

SECRET_KEY=yoursecretkey
DATABASE_NAME=yourdbusername
DATABASE_USER=yourusername
DATABASE_PASS=yourdbpassword

NOTE: there are no apostrophes around the values to the right of the equal signs. This is inteded and it should be like that.

It's probably also time now to add the .env to your gitignore file if you are using git so this entire process isn't pointless in the end ;)

Using environ settings

Now we just need to use our new setup in the settings file like so:

SECRET_KEY = env('SECRET_KEY')

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': env('DATABASE_NAME'),
        'USER': env('DATABASE_USER'),
        'PASSWORD': env('DATABASE_PASSWORD'),
        'HOST': '...',
    }
}

Summary

Both methods we have seen work equally well. Method 1 can be implemented "natively", but requires you to write more scripts to work. However, it works by a simple python function you wrote and know exactly what it's doing. Method 2 is more comfortable in a sense that you have one clear file where your secrets live no matter what operating system your django site is run on and on top you don't have to remember to run a script when you deploy to a new machine before running the django server. However, method 2 introduces a new dependeny to your app and as such a possible security vulnerability (which is every code you didn't write yourself).

Shortcomings of both: being it the .env file or shell-scripts: they both need to make it into your development environments or production servers. So you still need something like scp to push files to your server or a secure file sharing solution towards developers working on your project.