Managing multiple Heroku configurations
Using Fabric and Git to manage Heroku environment configuration.
As previously discussed YunoJuno runs on Heroku. Doing so means adopting
their 12-Factor App philosophy, which has worked out great for us, and makes the project very clean in terms of separating code from configuration. One of its core tenets (and a requirement for hosting apps on Heroku) is the use of environment variables to store configuration.
Heroku's toolbelt CLI application provides a simple way to view and edit your remote environment variables (which is where Heroku stores secrets, connection strings etc.). Below is an example config (edited, obviously):
$ heroku config --app XXXXX-XXXXX
=== XXXXX-XXXXX Config Vars
APP_NAME: XXXXX-XXXXX
EMAIL_HOST: mailtrap.io
EMAIL_HOST_PASSWORD: XXXXXXXX
ERRORDITE_TOKEN: XXXXXXXX
ERROR_PAGE_URL: XXXXXXXX
HIGHRISE_API_KEY: XXXXXXXX
HIPCHAT_TOKEN: XXXXXXXX
MAINTENANCE_PAGE_URL: XXXXXXXX
PERIMETER_ENABLED: True
QUEUE_EMAIL: False
$
If you want to change a setting, you simply call the config:add
command:
$ heroku config:add QUEUE_EMAIL=True --app XXXXX-XXXXX
Setting config vars and restarting XXXXX-XXXXX... done, v271
QUEUE_EMAIL: True
$
This is great, and really simple to use, however, coming from a slightly more
'enterprise' heavy background than many starter-uppers I was very keen to add
some controls around configuration management, and specifically to ensure that all environment variable changes were version controlled. In addition (and
again, this may be related to my background) I really didn't want live config
settings sitting in the core site repo. So we had the following requirements:
- Configuration settings sitting in a git repo
- Configuration repo separate from core codebase
- A mechanism for applying / validating environment changes
The first two of these requirements were pretty simple to do - we just pulled
out all the settings into a new repo, and applied the relevant controls to that - for instance, not everyone has rights to change live environment settings, even if they are admins of the core code repo.
The third requirement was a little more complicated, and is the real subject of this post.
We are great fans of Fabric, and have fabfiles in the root of all our repos
(including the blog repo, where I am now!), so using Fabric seemed like the
best option.
The result was a couple of Fabric tasks that work together to validate and diff
local and remote environment settings, and to apply local settings over the
remote settings if required. In practical terms this is a single fabric command,
heroku_set_vars
that will output any differences between what you have in the
local (version-controlled) repository, and the remote Heroku environment. It's
probably best explained with an example.
We have four remote environments (aside from any we spin up to test feature
branches): dev, uat, demo and live. They should be fairly self-
explanatory (demo is a copy of live, but with random data and is where we can
show clients the live site without having to go through the full onboarding
process, or messing up the live site with test data).
For each environment we have a single settings file, that is named {{env}}.conf,
where {{env}} is the environment name as outlined above. These contain all of
the version-controlled Heroku environment settings, as a JSON dictionary:
{
"APP_NAME": "XXXXX-XXXXX",
"EMAIL_HOST": "mailtrap.io",
"EMAIL_HOST_PASSWORD": "XXXXXXXX",
"ERRORDITE_TOKEN": "XXXXXXXX",
"ERROR_PAGE_URL": "XXXXXXXX",
"HIGHRISE_API_KEY": "XXXXXXXX"
"HIPCHAT_TOKEN": "XXXXXXXX",
"MAINTENANCE_PAGE_URL": "XXXXXXXX",
"PERIMETER_ENABLED": "True",
"QUEUE_EMAIL": "False",
"TEST_ENV_VAR": "Test"
}
If I run the fabric task heroku_set_vars:dev
, the task will look for the
settings file settings/dev.conf
(they are stored in a settings directory),
and then do a comparison between the settings in that file and the remote Heroku application as defined by the APP_NAME setting. Settings that are in both the local file and the remote environment, but differ in their value, are marked with a '!'; those that are in the local file but not in the remote environment (i.e. new settings) are marked with a '+'; settings that appear in the Heroku environment, but are not in the version-controlled settings file are listed, for reference, but are not included in the diff process. This is because most Heroku addons set environment variables of their own - these are controlled by the add-on provisioning process, and do not need to be version-controlled.
The output looks like this:
/config [master]$ fab heroku_set_vars:dev
Calculating settings diff for XXXXX-XXXXX:
[localhost] local: heroku config -a XXXXX-XXXXX
APP_NAME ["XXXXX-XXXXX"]
EMAIL_HOST ["mailtrap.io"]
EMAIL_HOST_PASSWORD ["XXXXXXXX"]
ERRORDITE_TOKEN ["XXXXXXXX"]
ERROR_PAGE_URL ["XXXXXXXX"]
HIGHRISE_API_KEY ["XXXXXXXX"]
HIPCHAT_TOKEN ["XXXXXXXX"]
MAINTENANCE_PAGE_URL ["XXXXXXXX"]
PERIMETER_ENABLED ["True"]
! QUEUE_EMAIL ["False" (local) != "True" (remote)]
+ TEST_ENV_VAR ["Test"]
The following keys were found in the remote environment but
have no local equivalent:
BONSAI_URL, DATABASE_URL, LIBRARY_PATH, [...]
The following settings will be applied:
QUEUE_EMAIL=False
TEST_ENV_VAR=Test
Please confirm your intention to continue by typing
in '96844' at the prompt:
[localhost] local: heroku config:set QUEUE_EMAIL=False
TEST_ENV_VAR=Test -a XXXXX-XXXXX
Setting config vars and restarting XXXXX-XXXXX... done, v270
QUEUE_EMAIL: False
TEST_ENV: Test
Done.
You may have noticed in the output that we also ask for a number to be input
by the user before making the changes. This prompt_for_pin
function is one
that we use whenever significant actions are taken (migrating data, deploying
applications). It just stops people from blindly hitting Enter and having some-
thing unfortunate occur. In fact, when deploying to the live site we have two
prompts, with a short ten second 'pause for thought' in between. Just in case.
And there you have - configuration management for Heroku. I've uploaded the
fabfile to Github as a Gist, for anyone to hack around to their own requirements.
Making Freelance Work
Posted in: heroku