12 Factor Rails Settings

Matteo Latini

5 Jul 2019 Development, Ruby On Rails

Matteo Latini

3 mins
12-Factor Rails Settings

Good apps are built following the The Twelve-Factor App principles and we've been adhering to those for some time now; problem is: Ruby on Rails doesn't always follow those principles in favor of ease of use and simplicity. One aspect that can be improved is configuration through environment variables.

ENV variables FTW

Before digging into the details, let's discuss a little bit about why environment variables are good for you:

  • code is identical no matter where you run it;
  • configuration is dynamic and not stored statically within code;
  • you don't need a commit to change a setting;
  • production configuration will be stored safely in a separate place;
  • environments are just a list of variables and are easy to replicate.

Of course the above can be obtained within what Rails already suggests, but fallacy is right around the corner and it's more tempting to just commit everything to the repository when using "The Rails Way".

The Rails way

As the RailsGuides explains, Ruby on Rails is mostly configured via static YAML files (like database.yml) and initializer files.

Some configurations can be handled via an environment variable (like RAILS_ENV and DATABASE_URL) but not much can be done with those, too few are available to actually configure a full application.

Why is this so popular:

  • ease of use;
  • really simple to understand;
  • powerful mix of configuration and Ruby code;
  • configuration is validated at app boot.

What are the limits of this approach:

  • code duplication;
  • potential configuration leak;
  • configuration could be committed in repo;
  • infrastructure must be built around the app;
  • initial setup of app requires cp some.yml.example some.yml.

Another thing worth mentioning that you can do in Rails is using encrypted credentials, but I find them almost impossible to work with: they feel like encryption has been used as a trade-off to dump every possible environment configuration within the application code without worrying too much.

Our approach

Over the years we've matured an approach that uses environment variables but still makes it possible to go through Ruby on Rails to get the best of both worlds.

We use config_for to load a config/settings.yml that is actually commited to the repo but loads configuration from ENV. An example file could be:

default: &default
  secret_key_base: <%= ENV.fetch('SECRET_KEY_BASE', 'some-default') %>
  host: <%= ENV.fetch('HOST', 'localhost:3000') %>
  s3:
    assets_bucket: <%= ENV.fetch('S3_BUCKET') %>
    access_key_id: <%= ENV.fetch('S3_ACCESS_KEY') %>
    secret_access_key: <%= ENV.fetch('S3_SECRET_ACCESS_KEY') %>
    region: <%= ENV.fetch('S3_REGION', 'us-west-1') %>
development:
  <<: *default
test:
  <<: *default
production:
  <<: *default

And we load this file with a custom helper built this way:

module Nebulab
  class Config
    class << self
      def fetch(key)
        settings = Rails.application.config_for(:settings)
        key.split('.').inject(settings) do |accumulator, subkey|
          accumulator.fetch(subkey)
        end
      end
    end
  end
end

So for example if you need to load a configuration within the application it's as simple as:

Nebulab::Config.fetch('s3.assets_bucket')

Common mistake: use dotenv to manage configurations

Some may ask: why can't you simply use dotenv to manage app configurations?

Well, for starters, dotenv is not a gem meant for application configuration management, but a gem that loads environment variables from an .env file. On multiple occasions I've seen dotenv used as a configuration manager and I've always seen developers fail spectacularly at keeping configurations in order.

In the end, using only dotenv for managing configurations is not worth it. We find our approach has some added benefits:

  • it's still plain Rails, no need for an extra gem;
  • the configuration is validated at application boot;
  • app boot will fail if a required variable hasn't been provided;
  • defaults are good for working on the app locally;
  • developers won't have access to production configurations;
  • you get a manifest of the required configurations;

So if you want to use dotenv, use it just to load in environment variables!

Conclusions

From our experience, this way of storing configurations for apps combines the best of both worlds and it has been battle tested on modern Docker-centered environments as well as traditional Capistrano-centered infrastructures.

That's all! Do you have any preferred way of storing configurations? We'd like to know. Got questions? Leave a comment!

You may also like

Let’s redefine
eCommerce together.