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:
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:
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
.
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.