Django Settings and Environment Variables

written by Chris Goodwin on 06/09/2023

Configuring a Django project can seem a little daunting, especially when it comes to managing environment-specific settings. For example, you will want to use different settings when developing locally versus settings you may want to have in a staging or production environment.

If we follow the 12-factor principles, it can make that task a little easier. To streamline this process in Django, we can leverage a powerful library called django-environ. In this article, we'll explore how django-environ can help align your Django project with the 12-factor principles by managing environment variables and separating configuration from code.

What are the 12-factor principles?

The 12-factor principles are a set of best practices for building modern, cloud-native applications. They provide guidelines for structuring and managing applications in a scalable, maintainable, and portable manner. One of the key principles is the separation of configuration from code. You can always visit the 12factor website for more information.

Installing django-environ:

Before we dive into the details, let's make sure we have django-environ installed in our Django project. You can install it using pip:

pip install django-environ

Once installed, we're ready to begin the process to enhance our Django project with better configuration management.

Using django-environ

Storing Configuration in Environment Variables:

Instead of hardcoding configuration values in your Django settings file, django-environ allows you to store them in environment variables. This not only adheres to the 12-factor principle of separating configuration from code but also makes it easier to manage different configurations for different environments.

To get started, create a .env file in your project's root directory. Add your environment-specific configuration variables in the file. For example:

# your local .env file
DEBUG=True
SECRET_KEY=your_secret_key
DATABASE_URL=postgres://username:password@localhost/db_name

Loading Configuration from Environment Variables:

Now that we have our configuration stored in environment variables, let's load them into our Django settings. Open your settings file (e.g., settings.py) and import env from django-environ:

import environ
import os

env = environ.Env(
    # set casting, default value
    DEBUG=(bool, False)
)

# Set the project base directory
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

# False if not in os.environ because of casting above
DEBUG = env('DEBUG')

# Raises Django's ImproperlyConfigured
# exception if SECRET_KEY not in os.environ
SECRET_KEY = env('SECRET_KEY')

# Parse database connection url strings
# like psql://user:pass@127.0.0.1:8458/db
DATABASES = {
    # read os.environ['DATABASE_URL'] and raises
    # ImproperlyConfigured exception if not found
    #
    # The db() method is an alias for db_url().
    'default': env.db(),
}

In the code above, we import Env from django-environ and create an instance called env. We cast DEBUG to a bool value that defaults to False. This casting can allow you to set sane defaults for important variables if you desire (such as setting DEBUG to False by default). We then read the env file and use env.bool() to read the DEBUG variable as a boolean value, env.str() to read the SECRET_KEY variable as a string, and env.db() to parse the DATABASE_URL variable as a database configuration dictionary. By doing this, we can dynamically load configuration values from environment variables.

Notes on .env files:

The .env file should be specific to the environment and not checked into version control. It is best practice documenting the .env file with an example. For example, you can also add .env.sample or sample.env (however you feel comfortable naming it) with a template of your variables to the project repo. This file should describe the mandatory variables for the Django application, and it can be committed to version control. This provides a useful reference and speeds up the on-boarding process for new team members, since the time to dig through the codebase to find out what has to be set up is reduced.

A good sample.env could look like this:

# SECURITY WARNING: don't run with the debug turned on in production!
DEBUG=True

# Should robots.txt allow everything to be crawled?
ALLOW_ROBOTS=False

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY=secret

# A list of all the people who get code error notifications.
ADMINS="John Doe <john@example.com>, Mary <mary@example.com>"

# A list of all the people who should get broken link notifications.
MANAGERS="Charles <charles@someorg.org>, Judy Smith <judy@someorg.org>"

# By default, Django will send system email from root@localhost.
# However, some mail providers reject all email from this address.
SERVER_EMAIL=webmaster@example.com

# Database
DATABASE_URL=postgres://username:password@localhost/db_name

Customizing Configuration for Different Environments:

django-environ provides a convenient way to customize configuration for different environments. You can create separate .env files for each environment (e.g., .env.dev, .env.prod, etc.) and load them based on the current environment.

In your settings file, you can modify the loading of environment variables as follows:

# Determine the environment (defaulting to 'dev' if not set)
ENVIRONMENT = env.str("ENVIRONMENT", default="dev")

# Load environment-specific variables
env_file = f".env.{ENVIRONMENT}"
env.read_env(env_file)

# Continue loading other configuration variables
...

In the code above, we introduce an ENVIRONMENT variable to specify the current environment. We then dynamically construct the filename of the environment-specific .env file and load it using env.read_env(). This allows you to easily switch configurations based on the environment you're working in.

Conclusion:

By using django-environ, you can align your Django project with the 12-factor principles and simplify configuration management. Storing configuration in environment variables allows for greater flexibility and portability across different environments. Additionally, separating configuration from code improves security and makes your project more scalable and maintainable.

Take advantage of django-environ in your Django projects and enjoy a streamlined and more robust configuration management experience.

Did you find this article helpful?

Feel free to help me keep this blog going (with coffee)!