Config vars and Heroku
by Trevor Turk
I don’t really care for the suggested approach in the Heroku docs for setting configuration variables locally. I have an open-source project that I’m working to get onto Heroku, so I decided to do a little work to come up with a solution that I prefer. I think this would work well for open source projects, as well as projects with multiple developers.
Here’s the basic idea:
You have a config file that contains all of your local configuration variables. It looks a lot like database.yml.
# config/config.yml development: session_key: example_development session_secret: ESl6X3oKM1i1RRrD2QLwUUzz9jr1zxNO domain: http://example.com test: session_key: example_test session_secret: vrwPpJTvwnMVLP1wTSgqigSl7PMI7QcE domain: http://example.com production: session_key: # any string identifying your app session_secret: # a random, secret string at least 32 characters long domain: # http://example.com mailer: # noreply@example.com
You perform a little trickery in environment.rb to prefer the Heroku ENV storage of config vars (in the production environment), but you fall back to your config.yml if the config vars aren’t found in ENV (in the development and test environments).
# config/environment.rb
Rails::Initializer.run do |config|
require 'yaml'
# support yaml and heroku config vars, preferring ENV for heroku
CONFIG = (YAML.load_file('config/config.yml')[RAILS_ENV] rescue {}).merge(ENV)
config.action_controller.session = {
:key => CONFIG['session_key'],
:secret => CONFIG['session_secret']
}
end
Then, you create a rake task (rake heroku:config) that can be used to send all of the config vars for your production environment up to Heroku. This task can be invoked once to set things up, but can also be run again if you need to make any additions or changes.
# lib/tasks/heroku.rake
namespace :heroku do
task :config do
puts "Reading config/config.yml and sending config vars to Heroku..."
CONFIG = YAML.load_file('config/config.yml')['production'] rescue {}
command = "heroku config:add"
CONFIG.each {|key, val| command << " #{key}=#{val} " if val }
system command
end
end
This way, you’ve got all of your config vars stored with the project (.gitignored, of course)…
# .gitignore /tmp/**/* /log/* *.log /tmp/restart.txt /config/config.yml /config/database.yml /db/*.sqlite3
…and you can easily set what you need on Heroku, like so:
$ rake heroku:config Reading config/config.yml and sending config vars to Heroku... Adding config vars: session_key => example_production session_secret => 1WlkMkYYi5611vtF...0ZMS2G3Xl67s4lEIK4sj65 domain => http://example.com mailer => noreply@example.com Restarting app...done.
The result is a pretty nice, I think.
You can see the installation and deployment instructions for my open source project El Dorado if you’re curious about the overall flow.
I’d love to get some feedback on this approach, but I really like it so far
The one thing I'd add, for open source projects at least, is to .gitignore that config/config.yml file. That'll make it as easy as possible to clone, setup, and deploy an app to Heroku, while keeping things easy to set up. I've meaning to do so with my Aintablog project (http://github.com/nakajima/aintablog) for a while now. Your post just reminded me.
Yeah, I totally agree, but I think I did include that in the example .gitignore above…?
That's pretty much what I did for mentors. config.yml and database.yml are ignored, config.example is included for devs to use, but in production we simple use ENV instead of the yml.
I naively "improved" this by issuing the `heroku config:add` separately for each variable. This fails silently: Only the last one appears to stick. I think it's related to the fact that `heroku config:add` triggers an app restart. Anyway, it's probably worth noting something about this in your script–why the way you did it, building up the command, and issuing it in one fell swoop, is important.
Thanks!
Mark
[...] Config var and Heroku [...]
[...] For configuration Heroku has a way but for some reason it didn't seem like the best or easiest way to do things. I started off setting up a configuration file as described in this railscast on using yaml configuration files. I took a while to dig a little more and found something similar but even better way of doing configuration with Heroku. [...]
Whenever you change the config, you have to rerake the task. Then do you verify that the change was made successfully? Also, is it a good idea to store sensitive info on your local machine?
Don't have a better solution yet, but I am still looking…
By the way, I don't see a date on this post. It's useful because Rails is changing so fast…