Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Configuring Rails Environments (joingrouper.com)
87 points by zimkies on Sept 5, 2014 | hide | past | favorite | 24 comments


Rails will have custom configuration built-in from 4.2, but you can also just use the gem today: https://github.com/dhh/custom_configuration

Then you can centralize all your configuration in config/environments/* and config/initializers/. The best practice being that you set the configuration in config/environments/ and you read that configuration point and do something with it in config/initializers/*.

IMO, much nicer than messing with ENVs.


My first thought after reading the OP, was to link to your blog post[0]. It was a real eye-opener for us, and we stopped using this `if Rails.env.production?` (anti)pattern in favour of configuration. Much cleaner.

I think a key aspect to make this successful is to make configurations 'inherit' from each other (as you explained on the post, using `require Rails.root.join("config/environments/production")`). It really makes our staging environment configuration only very slightly different from production and is very DRY.

[0] https://signalvnoise.com/posts/3535-beyond-the-default-rails...


Do you recommend 'installing' config/environments/* information from a provisioning utility when you're dealing with configuration values that should remain secret and outside of version control?


- "You cannot update your server’s configuration without committing and deploying new code"

I admit while I agree with many parts of 12-factor, I have never full understood the rational for this. What is the rationale of moving from versioned, concise config files, to un-versioned ENV variables?

- "Due to Ruby’s lack of type checking, the following code has a full code path that is not tested."

Add this to your specs: Rails.stub(:env).and_return('staging') ?

- "Bundler won’t install ‘production’ grouped gems on your staging server"

Use identical gems under staging, or override Capistrano's bundle install

- "It’s easy to make mistakes when configuring code by environment."

How about defining a new config value in staging.rb? Your::Application.config.mount_admin

I may be missing something, but how can ENV variables scale beyond a few config variables? I can see an argument that we should all be striving for fleets of micro-services that will only require a few, but many codebases cannot and won't ever realistically meet this goal.


No one says your env vars need be unversioned. For instance, at SeatGeek we version all env vars in our chef cookbook repository as databags and reference them in the application infrastructure.

It does require more fancy footwork when deploying new features that need environment variables, but it also lets us share our codebase with non-technical users and the occasional freelancer/potential FT hire without needing to scrub confidential information.


So why not just move the confidential stuff to ENV, and use the traditional Rails methods? What does ENV vs config get you?


> Add this to your specs: Rails.stub(:env).and_return('staging') ?

  allow(Rails).to receive(:env).and_return 'staging'.inquiry
This preserves the Rails.env.staging? behaviour. Rails.env is an ActiveSupport::StringInquirer


I've found a good use case is feature switches. If something goes flakey in production I can quickly disable a feature without editing files, and the full process around that.


At this point I think they are ready for a minor refactoring to name their code with what it's actually doing, ie. they aren't wrapping env, they are configuring their system, so let's have a Configuration class. For getting discrete values, this could look like:

    num_retires = Configuration.retry_attempts_count
which could fail immediately if the ENV['RETRY_ATTEMPTS_COUNT'] is not set.

While for configuring boolean values, we could do something like:

    show_confidant if Configuration.display_confidential_info?
Which could handle the extra care around true/false ENV parameter parsing by taking advantage of the question mark in the method name convention that Ruby has.

For bonus points wrap that into the existing Rails.application mechanism, so that it's an obvious place for new developers on the project to go to find app parameters.


In Rails 4.1, you can actually use ERb in the `secrets.yml`, so something like this should works:

    defaults: &defaults
      secret_key_base: <%= ENV.fetch('SECRET_KEY_BASE') %>
      default_hostname: <%= ENV.fetch('DEFAULT_HOSTNAME') %>
      redis_url: <%= ENV.fetch(ENV['REDIS_PROVIDER'] || 'REDIS_URL') %>
      debug: <%= ENV.fetch('DEBUG') == "true" %>

    development:
      <<: *defaults

    production:
      <<: *defaults
Though not as convenient as Figaro, I've been pretty happy with the setup so far.


This pretty much describes Figaro: https://github.com/laserlemon/figaro (apart from being baked into Rails, that is...)


Nice. I should have known to spend 5 minutes looking for a gem instead of writing a comment. I suppose the parent could have done the same before writing their ENV wrapping and a blog post. ;)


These points (fail-fast, types and one-stop-config) were exactly what motivated me to write the ENVied-gem (https://github.com/eval/envied#envied-).


Great gem, thanks for sharing!


To solve these kinds of problems and make more "12-factor"y Haskell apps I once wrote env-parser [0][1] though I haven't pushed it to release in a while.

Env-parser does a few things to make handling ENV easier. First, it, through virtue of Haskell's IO-concentration properties, forces you to pre-load your relevant environment variables and thus fail-fast due to misspellings and poor parses. Second, it tries to force you to interpret the ENV strings as typed values so that there's a limitation of the syntactic space allowable in ENV files.

Finally, and most importantly, it uses an "applicative functor structure" so that the exact code which parses your ENV can be used to produce documentation automatically. This is nice because it will always be in-sync with your program (modulo errors in my own coding of the library).

If anyone is interested in using this in their Haskell code, I'd be more than happy to push out the newer version. It has much nicer parsers and better reporting.

[0] http://hackage.haskell.org/package/env-parser [1] https://github.com/tel/env-parser-talk


That sounds great - push it up! Is there a github repo for it (hackage is downish at the moment).


tel/env-parser-talk has the most updated code. I'd love some discussion on feature engineering before I push it. Want to email me or leave an issue on that repo? I'll probably get some time this weekend to knock it out.


Despite using ENV all the time, this article made me actually question what the hell it actually is, i.e. how it is implemented. It quacks very muck like a Hash, so after reading this I thought all these solutions a bit ceremonious. Why not just set ENV's default proc to raise a KeyError to behave like Hash#fetch:

   ENV.default_proc = ->(_,key) { raise KeyError.new "Key not found: #{key.inspect}" }
Alas, ENV is not a Hash and doesn't have default_proc. It's an instance of Object, i.e. literally Object.new, decorated with extra methods, see https://github.com/ruby/ruby/blob/d738e3e15533e0f500789faaed.... Wow, TIL.

Anyway, a real simple way to have ENV[] behave like ENV.fetch is to define a method on ENV's metaclass:

  def ENV.[](key)
    fetch key
  end
This worked fine in irb but use fetch, intent is clearer.


Be careful when using environment to store sensitive info (keys, etc) - they are visible to other processes of the same user and also can be sent as part of crash report. See http://security.stackexchange.com/q/49725


I cannot help but find it funny that domain names are such as a rare commodity that many startups have domains that are different from their names like:

joingrouper getsomething getsomethingapp ... you get the point.

I don't think this is actually a good idea to name your startup this way. Paul graham had a good blog on startup names (see note). SEO space is polluted, people naturally can't remember what your URL is. While it might be ok for mobile apps, I certainly don't think this works for webbased apps.

Thoughts?

Note: http://aux.messymatters.com/pgnames.html


I always keep this in my boot.rb

    SAFE_ENV = Proc.new do |name|
      ENV[name].tap{ |value| raise "Environment variable #{name} is missing" unless value }
    end
and then for any required env vars I use SAFE_ENV instead of ENV e.g.

    SAFE_ENV['AWS_KEY'] # throws error if missing
    ENV['AWS_KEY'] # silently continues


I love this, I recently wrote about it too. It allows for a much easier time for new developers coming on to your project, and I think it makes everyone else have an easier time too.

http://www.wizard.codes/code-review-cleaning-up-the-environm...


I like it (and am doing something similar on a hobby django project), but will this be hard to maintain? Say you're spinning up a new non-heroku server or three. Are there good tools for moving the env variables without making mistakes?


Here's how I have come to address this issue (fyi, I'm using django-configurations and my own starter django project configured with it + ansible roles for deployments: http://github.com/powellc/outline ):

Your production app settings live in a separate repository that is kept much more secure than the other project files. These exist as host_vars files that you can simply drop in the host_vars directory in your ansible configuration and run the deploy script.

A sample host_vars can be included for either setting up a dev vagrant box or even your staging setup, if you don't have any really secret API keys.

That way you can move things around, spin up new boxes or what have you, simply by creating new host_vars files or moving them around.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: