Separate config and code Link to heading
When starting out on a fresh project we always prioritise speed over correctness/best practice. This is no more evident when it comes to configuration in code. Often in our eagerness to bring our idea to life we store our configuration as close to our code as possible. Things like database credentials and API tokens are hard coded as constants in our source code to reduce friction.
The Twelve-Factor App highlights this as a violation
of the twelve-factor app, which requires “strict separation of config from code”.
There are several approaches to implement the separation of config and code.
For example, in Django, configuration is included in the settings.py
module.
One approach would be to have a development settings file checked into the repo
and have a production module for when the application is deployed.
Whilst much better than storing database passwords in constant variables it is
still risky.
You could imagine with this solution how the production file could be accidentally
committed to a repo and exposed to the outside world.
A tidier solution would be to have a settings module that reads in environment specific
values from the environment.
In Python, os.environ
can be used to implement this solution.
There are other more sophisticated solutions, such as python-env.
For Django, we would have one settings.py
file which reads secret values from
the environment.
There are several ways to add variables to your environment.
Configuring Apps using Environment Variables Link to heading
An initial solution would be to add the secret environment variables to
.bashrc
or .zshrc
.
This would set the environment variables globally and doesn’t scale very well,
it becomes more difficult if you worked on two or more projects that had the
same variables to be set.
A better solution is to have a collection of scripts to set and unset project environment variables.
# set.sh
export DATABASE_NAME=database_name
export DATABASE_PASSWORD=my_secret_password
export DATABASE_USER=user
# unset.sh
unset DATABASE_NAME
unset DATABASE_PASSWORD
unset DATABASE_USER
When starting work on a project you would run the set.sh
script.
Once finished we’d run the unset.sh
script before beginning work on another codebase.
This is better than having global environment variables but requires the developer to remember to run the scripts before and after working on a codebase.
If only there was a way to do this for us automatically?
Using direnv for Development Link to heading
This is where direnv comes in. Once configured and activated it automatically sets and un-sets environment variables when the developer changes into the project directory.
direnv
works with both macOS and Linux.
It comes with a bash installer that simplifies the installation process.
curl -sfL https://direnv.net/install.sh | bash
Once installed direnv
then needs to be configured to hook into your shell.
The instructions for this can be found here.
To use direnv
simply create a .envrc
file in the root of project.
The .envrc
will contain all the environment variables you wish to export into the
environment.
export DATABASE_NAME=database_name
export DATABASE_PASSWORD=my_secret_password
export DATABASE_USER=user
As a security precaution direnv
won’t automatically activate these environment
variables initially.
On adding the environment variables to the .envrc
file the developer must run
direnv allow
to signal to direnv
that it is okay to export the environment
variables in the file.
This command only needs to be run if the file changes.
Now every time you change into the directory (or child directory) the environment variables will be automatically exported. Similarly, on leaving the directory the environment variables will be unset.
Since the application settings are being read from environment variables no code has to be changed to accommodate deployment to production.
direnv
has more settings to improve your workflow.
For example it has the edit
sub-command that will open the .envrc
file
in your editor choice and automatically run direnv allow
when the file is saved.
To take advantage of this sub-command ensure that the EDITOR
environment variable
is set.
export EDITOR=vim
direnv edit
In this example .envrc
will be opened with vim
, upon saving and exiting vim
,
direnv allow
is run.
direnv
also comes with a set of functions that make command tasks easier to complete.
PATH_add
is a function that can be used in .envrc
files to easily prepend paths
to the users $PATH
.
Imagine your project contains a bin
directory with a set of helper scripts
that you want to call only when working on your current project.
In the .envrc
file we could call the PATH_add
function to add ./bin
to
our PATH
only when in the project directory.
# .envrc
PATH_add ./bin
direnv
contains lots of other useful functions in its standard library.