Manage Your Project's Environment Variables

This post shares how to manage your project's environment variables for development, test, and production.

The Twelve-Factor App says store your config in the environment, and already explained very well.

We will manage our env using dotenv gem. This gem can be used with both Ruby project & Rails project, we will focus Rails here.

First install it, and create a .env file that contains something like this as a starting point:

# Don't modify this file. Create `.env.local` and put your overrides in there.

Prefer ENV.fetch("HOST") over ENV["HOST"] because it fails fast. You can provide default value in the block form:

ENV.fetch("HOST") { "localhost:3000" }

because it is faster.

  • .env

    Common ENVs across all environments.

  • .env.test

    ENVs for the test environment.

  • .env.development

    ENVs for the development environment.

  • .env.local

    ENVs for local environment (development, test).

  1. First, dotenv loads the .env first
  2. Second, dotenv loads the .env.{test, development}
  3. Finally, dotenv loads the .env.local

The old practice is to create something like .env.sample, .env.development.sample, and .env.test.sample. Then you copy those to .env, .env.development, and .env.test respectively. Also, you will need to copy them again on Continuous Integration. This adds up to 6 files that I consider as an overhead. Things should just work out of the box, so what I propose is to only work with .env and .env.local.

So don't put any sensitive information in .env and commit it to version control, easier setup for both the people you're working with and on the Continuous Integration. Here is an example change on how you can migrate to what I proposed.

During development, there are some ENV that will need to be real, like OAuth app id and secret.

Put your real (sensitive) values in .env.local at local:

# .env.local

And you MUST ignore this file to version control:

# .gitignore


Alought dotenv provides .env.test that you can put test ENVs in, but then we need to working with one more file and I bet this is rarely the case. And sometimes we will need to test when ENV changes.

So for the test environment, I suggest you provide a fake value by yourself in relevant places. Avoid use of env.test because it introduces Mystery Guest.

How do you manage environment variables & testing, please see this excellent post: Testing and Environment Variables.

Find out how to set/get/unset those ENVs on your production-like (staging, production) server, on Heroku is as simple as:

# Set a ENV
$ heroku config:set GITHUB_TOKEN=78a326b40bf6a7bfd80b0377445707523dfc4b299366942d19904fafe685afe6426d900c535741f8
Adding config vars and restarting myapp... done, v2
GITHUB_TOKEN: 78a326b40bf6a7bfd80b0377445707523dfc4b299366942d19904fafe685afe6426d900c535741f8

# Returns all ENV
$ heroku config
GITHUB_TOKEN: 78a326b40bf6a7bfd80b0377445707523dfc4b299366942d19904fafe685afe6426d900c535741f8

# Get a single ENV
$ heroku config:get GITHUB_TOKEN

# Unset an ENV
$ heroku config:unset GITHUB_TOKEN
Unsetting GITHUB_TOKEN and restarting myapp... done, v3

And if you're not using Heroku, you should try to make your experience Heroku-like.

Manage environment variables is easier than ever with dotenv. Just commit common .env, overrides in .env.local and you're good to go!

Happy Managing! :blush: