Deploying an app to Heroku is awesome. Really. If you have not yet done so, it is worth spending the few minutes to setup a simple Rails app and deploy to Heroku.
Awesome! Unless you have a lot of local configuration (perhaps your app talks to many external systems and has lots of settings). Then it becomes a bit less awesome, but we can fix that!
Heroku handles local configuration with something Heroku calls config variables. They are effectively environment variables, but I will follow Heroku’s lead and call them config variables.
Before Heroku, the issue of how to keep local configurations, like database settings (including passwords) and API tokens, was resolved by having a file that was not checked into source control, but that was avaiable in all environments.
An example we are all familiar with is Capistrano. One (or more) YAML (usually) files are setup locally on each host: your local development box, the staging server, the production server. On the deployment hosts (i.e. staging, production) these files are located in the Capistrano shared directory and symlinked during the deploy so that the application can read them to load any configurations.
Heroku applications cannot use local storage in the same way. There is no directory available to store your applications configuration files locally; and you do not want them in source control1.
Heroku instead provides each application with user defined config variables that are available in the your application’s
ENV hash. If, for example, you are configuring
omniauth with Twitter support, you might have this code in config/initializers/omniauth.rb:
Your app will read the configuration from the
ENV hash. You use the
heroku command to set the config variables:
heroku config:add TWITTER_KEY=key TWITTER_SECRET=secret --app myapp
This quickly becomes painful if you have to do this many times on many Heroku applications. After googling around for a bit, I found one article by Tammer Saleh that provided a bit of inspiration.
We can make things better by using a local file on your development machine to manage all the settings in one place.
Lets save our configuration to
config/heroku.yml which looks like this:
apps: staging: myapp-staging production: myapp development: &defaults admins: 'joe sue' domain_url: http://localhost:3000 twitter_key: key twitter_secret: secret test: <<: *defaults staging: bundle_without: development:test admins: 'joe sue' domain_url: http://myapp-staging.heroku.com twitter_key: key twitter_secret: secret production: bundle_without: development:test admins: 'joe sue' domain_url: http://myapp.heroku.com twitter_key: key twitter_secret: secret
The file should seem familiar; it is organized very much like the Rails
apps key defines a mapping from an environment name like
staging to a Heroku application like
Each environment key has its own settings. Note that
test are not named under the
apps key as they local and are not deployed to Heroku.
This file is not checked in and should be added to your
To load our YAML settings file at runtime, we have to modify
config/application.rb and add a new file:
config/heroku_env.rb (all files are on github).
We make the following modification to the top of
Our app is now reading our YAML file and populating the application’s
ENV hash based our settings file when we run locally.
To set the config variables on Heroku, we copy
heroku.thor (yes, github) to our
It defines two tasks for each of your Heroku apps:
config to set the config variables from the settings files, and
rack_env to set the
RACK_ENV config variable.
thor deploy:staging:config to setup our config variables on our Heroku staging application.
% thor deploy:staging:config heroku config:add ADMINS='joe sue' DOMAIN_URL='http://myappp-staging.heroku.com' TWITTER_KEY='key' TWITTER_SECRET='secret' --app myapp-staging Adding config vars: ADMINS => joe sue DOMAIN_URL => http://myapp-staging.heroku.com TWITTER_KEY => key TWITTER_SECRET => secret Restarting app...done.
And your app is now configured.
I have found this approach to managing my Heroku apps useful. One way it can be improved is by extending the
thor file to include common Heroku commands such as deploys (and you could use
invoke to chain together the deploy and configuration settings).
Still, I am not pleased. Mostly this is because I have not packaged this up nicely. The need for locally modified files makes this a candidate for a Rails generator, something I may do if I get the time.
For now the raw source is up on github.