Skip to content

Environment Variables

Environment variables, env vars for short, are key-value pairs that may be used to provide configuration to applications in the current runtime context. Some of them are used by the shell itself, while others are used by other applications you run.

They are useful to separate application configuration from application code, which allows you to run the same code in different environments, as well as to share your code with others while protecting sensitive information like API keys, and other details. This is in line with the Twelve-Factor methodology for application development, and as we will see, it can greatly improve your Django apps' security and your DX.

Environment variables and the shell

You can list all the current environment variables in your terminal with the printenv command. As you may notice, most variable names are written in all capital letters.

The shell will typically substitute env vars with their values before running a command if they encounter one. To use an env var in a command, you must prefix it with $.

This allows us to do things like print a specific env var's value:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Note

Your output is very likely to differ from this example.

As discussed in the introduction to the terminal, this variable holds a list of directories to search for executable commands, delimited by colons (though your shell may separate them with spaces instead). If you want to change the list, you can append a new directory to the variable, or even prepend it in order to give it a higher priority. For example, activating a Python virtual environment will prepend the PATH variable with the directory containing the python binary installed in the virtual env.

Environment variables and Python

Python exposes env vars using the environ mapping object (or dict) that is available in the os module. For example:

>>> import os
>>> os.environ['PATH']
'/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'

Of course, this may result in a KeyError exception, so you should use the get method whenever possible.

>>> os.environ["BAD_ENV_VAR"]
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
KeyError: 'BAD_ENV_VAR'
>>> print(os.environ.get("BAD_ENV_VAR"))
None

Note

Environment variables are strings and may need to be converted to a different type, e.g. integer for a port number, or boolean for a flag.

Setting environment variables

Env vars can be set globally, for the entire system, with certain files like /etc/environment or /etc/profile, depending on your distro. Personally, I would recommend not messing about with those unless you're sure you have a good reason.

They can also be set in a user's environment, including the graphical environment, within the context of a shell and its descendants, within the context of an application, etc. We will focus on the terminal in this book, but you can refer to the ever glorious Arch Wiki for information on other contexts.

You can temporarily set an env var for one command with the format VAR=value command, aka in-line. For example:

$ echo hello $FOO world
hello world
$ FOO=Django echo hello $FOO world
hello Django world
$ echo hello $FOO world # notice it is no longer set
hello world

Note

Not all shells allow you to set env vars in-line for built-in commands. This previous example may not work in bash, but it works in fish at least, and does work in bash with commands other than echo.

Setting env vars in bash

You can set env vars for the current shell with the assignment operator (=). This variable will not be made available to child processes, i.e. other commands.

$ BAR=Django
$ echo hello $BAR world
hello Django world
$ python -c "import os; print(os.environ.get('BAR'))"
None

You can set env vars that will be shared with child processes by using the export command.

$ export BAZ=Django
$ echo hello $BAZ world
hello Django world
$ python -c "import os; print(os.environ.get('BAZ'))"
Django

Persisting env vars in bash

Note that our previously set env vars will not be set if we close the terminal and open a new one. To persist env vars, we can set them in ~/.bash_profile or ~/.bashrc.

Simply add a line export VAR=value.

export FOO=Django

Setting env vars in fish

To set env vars in fish, simply use the set command. You can use set -g FOO Django to set it globally for the current shell, set -x FOO Django to export it to child processes, and set -U FOO Django to persist the variable across the current and future shell sessions.

.env files

It's fairly common to use a .env file to define env vars, and a package like django-environ or python-dotenv to read the file. Personally, I do not recommend it, as actually using environment variables makes certain things, like CI/CD integration, easier.

Env vars and Django

As mentioned, env vars are a great way to easily and securely configure Django projects. Simply define variables with reasonable names, and read them in your settings.py file wherever applicable.

# settings.py
import os

SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
# Note that env vars are strings, so we need to use a string comparison for booleans
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"

Gotchas

Remember that any non-empty string is truthy in Python, so the string "False" typecast to boolean will give the value True if you omit the comparison operator.

You can use this for database credentials, API keys, etc.

django-classy-settings

From the project's author:

Django Classy Settings Overview

Class-based settings make it easy for you to manage multiple settings profiles for your Django project, without ever copying values.

Interdependant values, values sourced from the env, even calculated values are no problem, since you have the full power of Python and class inheritance.

Django Classy Settings (cbs) makes using environment variables very simple, allows you to easily add conditional apps and middleware to your Django projects, handle type conversions, and more.

As cbs uses environment variables rather than files, I use it for my own projects and will use it throughout this book.

direnv

While I generally do not recommend adding third-party dependencies to your Django projects to load env vars from files, there is one exception I find extremely useful for development, direnv.

direnv is a shell extension that can load or unload environment variables based on your current directory. This means that you can have files such as ~/projects/foo/.envrc and ~/projects/bar/.envrc with distinct variables specific to a particular project. Entering the directory via the cd command will load the appropriate .envrc file.

Simply install the direnv package on Arch Linux or Ubuntu, configure the hook for your shell as instructed on the direnv site, create a hidden .envrc file with a list of variables using export VAR=value, and run direnv allow to authorize the file.

While direnv supports bash, fish, tcsh, and zsh, all files must use the bash syntax, as it loads the variables via a bash sub-shell.

I use this feature in most of my Django projects during development, and highly encourage you to look into it as well. In fact, we will use it when creating a Django project in the Git section.

Stay safe

Be sure to add the .envrc file to your .gitignore file as documented in the Using Git chapter so you do not accidentally commit it to the repository, as this file likely contains secrets that should not be shared.