Dotenv for multiple environments

Lately I’ve been handling configuration entirely through environment variables for my apps, as the 12 Factor App recommends, and I can’t recommend this approach enough. As a constraint it helps me think about what parts of a given app actually need to vary from environment to environment, as opposed to what parts could potentially vary. The app I’m working on right now has zero YAML configuration files. It doesn’t even have a config directory.

The Dotenv gem has been essential to making this mostly config-less strategy workable. All of my development environment variables are contained in a .env file. I exclude this file from Git since it contains various API keys that are specific to me and my development setup.

The other day I realized I needed my setup to be a little different when running in the test environment. Specifically, I needed a different DATABASE_URL. Everything else, though, was the same as the development environment.

I did a little poking around, and discovered that while it’s not very well documented, Dotenv.load can accept multiple filenames. Here’s the code I ended up with in my startup file:

require 'dotenv'

Dotenv.load(
  File.expand_path("../.#{APP_ENV}.env", __FILE__),
  File.expand_path("../.env",  __FILE__))

When running in a test environment, this code loads both a .test.env file (if it exists) and a .env file. The resulting ENV is a merge of both files.

A few notes:

  • When multiple files contain a variable with the same name, the leftmost file “wins”. So I put my “more specific” file on the left.
  • Dotenv takes care of checking to see if the file exists before loading it, so I don’t have to add any extra code to do that myself.
  • The dotenv-rails gem already has this built-in. My app is based on Sinatra and all of the startup is hand-rolled, so I needed to figure out how to do it manually.