Kristian Glass - Do I Smell Burning?

Twelve-Factor Config: Misunderstandings and Advice

At PyCon UK 2014 I gave a talk about The Twelve-Factor App, “a methodology for building software-as-a-service apps”. The Twelve-Factor stance on config - “store [it] in the environment” - is probably the most misunderstood.

I want those misunderstandings to stop. If you feel you disagree with 12factor, or you feel you don’t quite see the benefit or how to do it, then please keep reading. If you’re still not sure afterwards, drop me an email, or find me on IRC; I’d love to talk more.

If you take just one thing away from here, please make it this: 12factor says your applications should read their config from the environment; it has very little to say about how you populate the environment - use whatever works for you

What is config?

Config is everything likely to vary between deploys. That means service URLs, account credentials, debug flags, etc. Core app settings like URL routes are not “config” in this sense.

Why do this?

It’s language and framework independent.

You may be happy with some sort of library containing all of your config, shared across your apps - e.g. a Python module that you declare as a dependency.

You’ll be less happy when you want to deploy something in some different language that also wants to read some of that config.

You’ve separated your responsibilities.

Your app should not have environment-switching logic, or actively look up this data from some central service. It should be passed config at startup, and environment variables are a significantly cleaner interface than command line flags.

It helps avoid leakage of secrets.

I spend a lot of time in #django on freenode, and people pastebinning their Django SECRET_KEY, despite the warning on the line before it (or, more tediously, people self-censoring their settings to the point of uselessness).

“The environment is right there”

So you nod along to the above and then think “well I’ll just read from a YAML/JSON/properties/XML/$other file!”

Where do you find that file? Hardcode the path? Ew. Command line argument for the location? You’ve now added another route for inconsistency between apps.

The environment is right there. Use it.

But where do you populate the environment from?

One of the biggest misinterpretations is people thinking 12factor says “config should never be in a file anywhere”.

12factor says config should not be in a file in your app, and your app should read from the environment. It has very little to say about where the environment might be sourced from.

You’ll almost certainly have your config in some files somewhere.

Update: This may be hiera if you use Puppet, or via some other configuration management system, or just plain files but in a different repo. 12factor primarily talks about building apps and not about building the environment they are deployed to.

The point is that:

  1. Wherever you store your config, it is distinct from the app
  2. Your app reads its config from the environment

What do I use to configure the environment?

Building up a long command-line like FOO=x BAR=y BAZ=z mything gets old fast.

Fortunately there are many helpful tools:

For local development, foreman or honcho come in handy.

There’s djb’s envdir, and jezdez’s Python clone, also called envdir, both of which are great for just dropping in anywhere.

If you use supervisor, it has support for setting the environment of subprocesses.

You could even just use bash (after making sure you’ve updated it with fixes for shellshock!)

If you’re using Heroku it’s just heroku config:set; if you’re using Elastic Beanstalk it’s Option_settings

There are lots of tools to help; use whichever works best with wherever you’re deploying to.

Summary

Read your config from the environment, but populate the environment in any way you may choose.

Comments