My web site settings file is broken so I might as well fix a few things.
My website is not interactive so I have disregarded the rule that you shouldn’t put production settings in the source-code control system. I did this because it simplified my deployment system (described in an earlier entry). Since it is currently broken anyway I might as well take the effort to move it a little closer to best practices.
Part 2 of the Running to Stand Still series.
Config in the Environment
The Twelve-Factor App describes ways to ensure your a web app is
deployable, scalable and maintainable. One factor is that you store config in
the environment, rather than in files. For example, an environment
variable DATABASE_URL
specifies the connection settings for your database.
Where the environment variables come from depends on your hosting environment. They might be an env dir or some special database or parameters in a Dockerfile or Kubernetes chart or whatever.
Django Settings
With a Django app the settings are provided as a Python module.
The obvious approach to making this get the salient information from environment variables
is to make liberal
use of os.environ.get
in the settings file.
For example
DEBUG = os.environ.get('DEBUG')
When representing a boolean condition with environment variables I prefer to use an empty or absent value to represent false and a non-empty value to represent true.
With this convention it is best if false represents a default, safe, option:
No access to real databases without the DATABASE_URL
environment variable,
and so on. If we can contrive to make the defaults suitable for running a
staging copy of the site then this will make it easy to get set up as a
developer (more of a consideratuion when you have more than one developer).
There are packges like dj-database-url
that have routines for decoding
DATABASE_URL
in to the settings Django uses.
Django-environ
A more convenient solution is a third-party library django-environ
. This
wraps up a bunch of conveniences for getting settings from the environment in
one easy lump. It can read from a local .env
file to make development
straightforward, and knows how to decode database URLs.
Compared to my previous setup, this means replacing the sample settings and production settings files with a proper settings file starting with something like this:
import environ
env = environ.Env(
DEBUG=(bool, False),
STATIC_ROOT=(str, None),
STATIC_URL=(str, None),
)
environ.Env.read_env()
DEBUG = env('DEBUG')
…
if env('STATIC_ROOT'):
STATIC_URL = env('STATIC_URL', default='http://static.alleged.org.uk/')
STATIC_ROOT = env('STATIC_ROOT') # e.g., '/home/alleged/static')
else:
STATIC_URL = '/s/'
A less trivial web site would have more settings—such as for databases and caches—but this is still useful even for my tiny site.
For the most part I have arranged it so when safe enough the default is
developer mode. The exception is DEBUG
, so on my working copy I needed to do
something like this:
echo DEBUG=y >> alleged/.env
I also needed to make arrangements for the SECRET_KEY
setting. Normally it is
important that SECRET_KEY
be unique and secert, but since my siute does not
use user sessions I don’t actually use it. I have set it so if you are in
DEBUG
mode it uses a dummy value, and if not it treats SECRET_KEY
as a
required parameter, and aborts the server if it does not find it in its
environment.
Lessons
It is easier than ever to avoid checking database settings and keys in to source-code control. So don’t do that.