Heroku recommends configuring your application using environment variables instead of encrypted files that you check into your git repository. Environment variable-based configurations can be changed without requiring a git check-in and a new deployment.
Rails has the concept of different environments. By default, there are “test”, “development”, and “production”. Each environment has its own configuration file in the config/environments
folder, allowing you to specify custom behavior. Heroku is a production environment, so we recommend using RAILS_ENV=production
for all apps, including staging and review apps.
This article will describe problems with using a custom environment, such as RAILS_ENV=staging,
and how you can configure your application with environment variables instead.
Problems with Rails.env
It is common to change behavior based on a different environment in Rails code. For example:
if Rails.env.production?
# ...
else
# ...
end
Most developers expect that code defined in a Rails.env.production?
will run on Heroku. But it will not, leading to very time-consuming debugging sessions.
One option is to add another check to every location:
if Rails.env.production? || Rails.env.staging?
# ...
However, Rails.env does not check whether the environment exists. Therefore, it is prone to spelling errors, like accidentally using Rails.env.stage?
(instead of staging?
).
An alternative is inverting the logic:
if !Rails.env.development? && !Rails.env.test?
# ...
else
# Production AND staging
This removes the chance of spelling mistakes. But both options require the developer to remember that they need to use this non-idiomatic pattern. It also requires constant vigilance, as accidentally only using Rails.env.production?
may allow a bug to go into staging undetected and then crash your production server.
Even if your own application always uses the correct environment, any gems or libraries that expect to be able to use Rails.env.production?
as a source of truth for will experience different behavior on your staging app.
Heroku supports configuring your RAILS_ENV
to any value, even if we do not recommend it. Your application may encounter other problems that aren’t listed here, and you are warned that Heroku will not be able to provide you with custom application debugging for behavior differences or failures due to custom environments.
Problems with bundler groups
Bundler is environment-aware and will allow you to only load certain gems in certain environments:
gem 'mini_histogram', group: "production"
Introducing a new environment may create a scenario where some dependencies are installed on one application with RAILS_ENV=staging
, but not on another with RAILS_ENV=production
(or the other way around).
Problems with config keys
Rails will expect you to have keys matching your RAILS_ENV
for various configuration files, such as database.yml
. These configurations can diverge unexpectedly unless you’re rigorous about updating two locations.
Problems with config files
When Rails boots, it will load config/environments/<RAILS_ENV>.rb
. If this is not found, Rails will still try to boot with the default configuration, which will give the impression that running RAILS_ENV=staging rails serve
works locally but fails unexpectedly on Heroku.
Heroku depends on some values and behavior that are expected in config/environments/production.rb
. These are needed to configure the application to write output to standard out (stdout
) such that Heroku logs will work correctly, and you can debug when your deploy is failing. It’s possible to reuse that config file:
# config/environments/staging.rb
require_relative "production.rb"
Solution
The main goal of using a custom environment, such as RAILS_ENV=staging
, is usually isolation from the real production system. Developers want to run in an environment where they can safely validate new features and catch runtime crashes before they reach their users. They want to do this without the worry that they’re accidentally sending out emails, deleting real production database records, or putting extra load on their val-key (or Redis) instance.
To help validate changes before they are live to customers, Heroku provides CI, pipelines, and review apps. We recommend you configure these based on environment variables. Heroku provides add-ons that can be used to provision temporary resources, such as databases, for a short period of time. If your production application is already configured to use environment variables such as DATABASE_URL
, then it will automatically pick up a new add-on on a different Heroku application.
If you previously wrote code like this:
if Rails.env.staging?
S3_BUCKET = "my_staging_bucket_name"
end
You can instead configure that via an environment variable that can be set differently for each application.
S3_BUCKET = ENV['MY_BUCKET_NAME']
The email example mentioned above can be avoided by changing email provider credentials, provided they are in environment variables, or even setting your provider to use a “test” mode. For example, you can tell Mailgun not to send out emails.
Any configuration you wish to change in config/environments/production.rb
can be set from environment variables instead of being hardcoded and checked into git.
Problems with using more environment variables
Creating many environment variables for configuration switching creates a new problem of more environment variables required to run your application locally. The heroku local
command supports setting environment variables with a .env
file. Other gems and libraries support similar behavior, such as the dotenv gem.
Many configuration options can remain hardcoded in development and test environments to reduce the number of environment variables required to run your application locally. However, they’ll be required if you try to run in production
mode locally, for example:
$ RAILS_ENV=production SECRET_KEY_BASE=12334 RAILS_SERVE_STATIC_FILES=1 RAILS_LOG_TO_STDOUT=1 bundle exec rails serve