Environment Variables in Ruby on Rails

by Andrey KoleshkoFebruary 15, 2013
This blog post provides instructions on how to define environment variables in a Ruby-on-Rails app.

Looking for ways to define variables

When you see configuration examples in README files of gems as shown below, what do you think they are?

AssetSync.configure do |config|
  config.fog_provider = 'AWS'
  config.fog_directory = ENV['FOG_DIRECTORY']
  config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
  config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']

  # Don't delete files from the store
  # config.existing_remote_files = "keep"
  # Increase upload performance by configuring your region
  # config.fog_region = 'eu-west-1'
  # Automatically replace files with their equivalent gzip compressed version
  # config.gzip_compression = true
  # Use the Rails generated 'manifest.yml' file to produce the list of files to
  # upload instead of searching the assets directory.
  # config.manifest = true
  # Fail silently.  Useful for environments such as Heroku
  # config.fail_silently = true

Where should you define ENV['FOG_DIRECTORY']? Should you define it in a shell script (such as ~/.bashrc, ~/.bash_profile, ~/.profile, etc.) or in /etc/environment? If you think so, I have to disappoint you—you are wrong!

Let’s define the FOG_DIRECTORY variable in the ~/.bashrc file. Pay attention if ~/.bashrc includes the [ -z "$PS1" ] && return line, otherwise you have to define the variables above it.

export FOG_DIRECTORY=my-bucket-name

Then, reboot the shell and start a Ruby-on-Rails app. You will see that the app is configured correctly. You enjoy it and are going to deploy the app to production on VPS or VDS the same way. Everything is expected to work before the first reboot. Why ENV['FOG_DIRECTORY'] is nil after server reboot? The answer is simple—nginx or another web server (I don’t know which one you use, but I prefer using nginx) starts before evaluating ~/.bashrc and even /etc/environment.

If you use assets_sync to upload your assets to the cloud, you can have an issue with Capistrano during deployment.

AssetSync: using default configuration from built-in initializer
rake aborted!
Fog provider can't be blank, Fog directory can't be blank

Tasks: TOP => assets:precompile:nondigest
(See full trace by running task with --trace)

So, we have to look for another way how to define these variables.


The solution

Considering the problem above, there is a reasonable question: what are these variables in the config and what to do? How should we define these variables? While surfing the Internet, I’ve found a good article that explains scenarios how to achieve our goals. Now, I will describe the scenario I think is much simpler and faster than others.

Insert the following lines of code to config/application.rb after the config.assets.version = '1.0' line.

config.before_configuration do
  env_file = File.join(Rails.root, 'config', 'local_env.yml')
  YAML.load(File.open(env_file)).each do |key, value|
    ENV[key.to_s] = value
  end if File.exists?(env_file)

Now, you have to create the yml file in the config folder and add it to .gitignore if you have to define these variables locally. Key values are not real, so it doesn’t make sense to paste them in your configuration files. The example of config/local_env.yml is shown below.

FOG_DIRECTORY: my-bycket-name
FOG_REGION: eu-west-1

Finally, if we deploy the app with Capistrano, we have to deploy it properly. We should put local_env.yml to the Capistrano shared folder on the server and change config/deploy.rb as exemplified below.

before 'deploy:assets:precompile', :symlink_config_files

desc "Link shared files"
task :symlink_config_files do
  symlinks = {
    "#{shared_path}/config/database.yml" => "#{release_path}/config/database.yml",
    "#{shared_path}/config/local_env.yml" => "#{release_path}/config/local_env.yml"
  run symlinks.map{|from, to| "ln -nfs #{from} #{to}"}.join(" && ")

It is assumed that local_env.yml exists in the {shared_path}/config/ folder on the server.

You have just explored how I’ve done the same thing with my database.yml config (by the way, ignoring the database.yml in your concurrent version system is a best practice, as well). The real-world example can be found in the README file of the awesome assets_sync gem.


Further reading


About the author

Andrey Koleshko is a Ruby developer at Altoros. He is majoring in delivering payment gateway solutions, as well as systems for billing, invoicing, subscriptions, bookkeeping, and accounting. Find him on GitHub.