Rails Environments

Configuring Rails Environments

Despite its strong opinions and powerful conventions, Rails is a highly flexible and configurable framework. If you don’t like something, there’s almost certainly a way to change it. This article provides a brief overview of configuring Rails applications and environments.

9 min read

All Rails applications come with three environments out of the box: development, test, and production. In addition, you can also create your own environments for staging, QA, or continuous integration.

All these environments are highly customizable. They share a few standard settings and can have their own, custom settings, which allow you to configure things like your database connection, error handling mechanism, logging level and format, security-related settings, and much more.

In this post, we will learn how Rails environments work, and some of the useful configuration settings you can use to customize them. It doesn't cover all the configuration settings, but the most important ones that you'll often run into.

Here're the topics this article covers:

All Rails configuration-related files live under the config directory and most of the options are well-documented, either in code, guides, or the API. So feel free to browse the files and get familiar with the powerful settings available to you.

We'll start by inspecting a handy Rails command that gives a birds-eye view of your application environment.  

About Your Rails Application

The about command in Rails gives you a quick overview of your application and the current environment. It lists the versions of various components (Ruby, Rack, etc.), print all the middleware, and other useful information like database adapter.

➜  railsway git:(main) ✗ bin/rails about
About your application's environment
Rails version             7.0.4.2
Ruby version              ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-darwin21]
RubyGems version          3.3.26
Rack version              2.2.6.2
Middleware                ActionDispatch::HostAuthorization, Rack::Sendfile, ActionDispatch::Static, ActionDispatch::ServerTiming, ..., Rack::Head, Rack::ConditionalGet, Rack::ETag, Rack::TempfileReaper
Application root          /rails/railsway
Environment               development
Database adapter          sqlite3
Database schema version   0

P.S. I just learned this while writing and researching this article, after reading about Laravel's php artisan about command, which is pretty cool.

How to Create and Use a Specific Environment

You can specify the current environment using the RAILS_ENV environment variable. Its value is set to the name of one of the configuration files under the config/environments folder, i.e. development, production, or test. You can also create a staging.rb file in the environments directory and set RAILS_ENV to staging.

If the RAILS_ENV variable is not set, Rails checks the RACK_ENV to figure out the current environment. If that isn't set either, Rails assumes the environment is development by default. Rails will automatically set the RAILS_ENV to test when running tests (see test_helper.rb).

That means you don't have to set this environment variable during development or testing. The only time you have to worry about setting the environment variable is on your production server.

Sometimes, you may want to set up additional environments to set up a CI/CD pipeline or use a Staging server, before you deploy to production. In this case, all you have to do is copy and paste one of the existing configuration files in the config/environments folder and rename it to the new environment, e.g. staging.rb. Then configure it however you want with the settings described below.

Figure out the Current Environment

You can check the current environment using the Rails.env method.

➜ bin/rails c
Loading development environment (Rails 7.0.4.2)
irb(main):001:0> Rails.env
"development"

Rails also provides a bunch of useful helpers allowing you to assert a specific environment.

➜ irb(main):002:0> Rails.env.production?
false

➜ irb(main):003:0> Rails.env.development?
true

Behind the scenes, it uses the EnvironmentInquirer which extends StringInquirer  to enable these helpers.

# railties/lib/rails.rb:71

def env
  @_env ||= ActiveSupport::EnvironmentInquirer.new(ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "development")
end

This code also shows the sequence and priority of how Rails checks for the environment. As we saw in the previous section, it will first check RAILS_ENV, then RACK_ENV and finally, it will assume development environment.

Using Gems Specific to an Environment

The root of your Rails application contains a Gemfile which lists all the Ruby gems your application depends on, including the Rails gem.

# frozen_string_literal: true

source "https://rubygems.org"
gemspec

gem "rake", ">= 13"
gem "stimulus-rails"
gem "turbo-rails"

If you want to load a gem only in a certain environment, you can scope it using the group method.

# load `rake` in all environments
gem "rake"

# load `minitest` only in the `test` environment
group :test do
  gem "minitest"
end

# load `debug` in both `test` and `development` environments
group :test, :development do
  gem "debug"
end

When the Rails application loads, it requires all the gems listed in the Gemfile, including any gems you've limited to the specific environment.

Using Rails Initializers for Configuration

An initializer is simply a piece of Ruby code under the config/initializers directory. You can use initializers to configure your Rails application or external gems.

To learn more about Rails initializers, check out the following post:

A Brief Introduction to Rails Initializers
After loading the framework and any gems in your application, Rails runs the initializers under the `config/initializers` directory. This article explains how initializers work and how they’re implemented.

Out of the box, Rails provides following initializers:

  1. assets.rb: configures the asset pipeline.
  2. content_security_policy.rb: defines an application-wide content security policy.
  3. filter_parameter_logging.rb: configures the parameters to be filtered from the log file.
  4. inflections.rb: adds new inflection rules.
  5. permissions_policy.rb: defines an application-wide HTTP permissions policy.

Since Rails loads initializers after the application and external gems are loaded, initializers provide a great place to configure the application as well as the gems. You can find a comprehensive list of all initializers in Rails on the official Rails guides.

Common Configuration Settings in Rails

This section covers some of the important configuration settings that are common to most Rails applications. After inspecting the common settings, we'll dive into environment-specific configuration.

The config/application.rb file contains the common application configuration for all environments.

# config/application.rb

module Blog
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 7.0

    # Configuration for the application, engines, and railties goes here.
    #
    # These settings can be overridden in specific environments using the files
    # in config/environments, which are processed later.
    #
    # config.time_zone = "Central Time (US & Canada)"
    # config.eager_load_paths << Rails.root.join("extras")
  end
end

Note: The configuration settings defined in the config/environments/*.rb files take precedence over the ones defined in the config/application.rb file, as those files are loaded after loading the framework and all the gems.

Additional Common Settings

config.time_zone allows you to configure the time zone for your application. It's set to UTC by default.

config.generators lets you configure the Rails generators to avoid having to provide command-line flags repeatedly.

config.generators do |g|
  g.orm             :data_mapper, migration: true
  g.template_engine :haml
  g.test_framework  :rspec
end

config.log_level overrides the default log level, which is :debug.  You can set different log levels in different environments, depending on your needs.

config.log_level = :info

config.active_record.schema_format specifies the format to use when dumping the database schema.

config.active_record.schema_format = :sql

config.autoload_paths provides the array of directories from which we autoload and reload, if reloading is enabled. You can add other directories to this path, if needed.

Environment-specific Configuration

It's often helpful to have different configuration settings based on the environment where the application is running. For example, you may want to use a different Active Storage service in production than the one you're using in development.

Rails allows you to define environment-specific configuration settings in the appropriately named files in the config directory, e.g. the config/environments/test.rb file contains all the settings used in the test environment.

Development Mode

This is the default Rails environment. You can find all the settings specific to this environment in the config/environments/development.rb file.

config.cache_classes

The great thing about Rails is the quick feedback loop. Make a change, reload the browser, and see your change. This setting controls if the code should be reloaded any time it changes.

It's set to false in development mode, as we do want the code to be reloaded. However, it's enabled in production, as we won't be editing code in production and reloading adds extra cost.  

config.eager_load

When the application boots, Rails can eager load all the code. This is great for production (where this setting is enabled), but not so much for development.

You'll be continuously changing the code in development, and there's no point in eager loading the code, which increases the boot time. Hence it's set to false in development mode.  

config.consider_all_requests_local

In development, if something goes wrong, you need to see all the details regarding the request, form data, backtrace, line numbers, etc. However, in production, your users don't need this information. All they'll need is a nice error page.

This setting, when enabled prints a developer-friendly message that includes all the information needed for debugging. Set to true in development mode, but false in production.

config.server_timing

During development, it's important to see how long a certain request took. The Server-Timing header communicates this information for a given request-response cycle, which you can inspect in the Network tab in DevTools.

The server_timing setting, when enabled, adds the ActionDispatch::ServerTiming middleware in the middleware stack. It subscribes to all ActiveSupport::Notifications and adds their duration to the Server-Timing header.

This is a relatively new setting that was introduced in Rails 7.

config.perform_caching

Caching lets you save data generated during the request-response cycle and reuse it when responding to similar requests.

By default, caching is only enabled in your production environment. You can play around with caching locally by running rails dev:cache, or by setting config.action_controller.perform_caching to true

config.active_storage.service

This setting tells Active Storage which service to use. Since each environment will use a different service (local in development, s3 in production, etc.), it is recommended to do this on a per-environment basis.

In development, it's sufficient to store the uploaded files locally. Hence, it's set to :local service, which is defined in the config/storage.yml file.

config.active_record.migration_error

You must have seen the error Rails shows you when you try to run the app after adding a migration, but forget to run it. This setting controls this functionality.

In development, it's set to :page_load, which instructs Rails to insert the CheckPending middleware into the middleware stack. This middleware verifies that all migrations have been run before loading a web page.

config.active_record.verbose_query_logs

When set to true, this setting tells Rails to highlight the code that triggered database queries in logs.

config.assets.quiet

When set to true, this suppresses logger output for the asset requests. In development mode, it's not really useful. If necessary, you can turn it on for debugging purposes.

Test Mode

config.public_file_server

In test mode, this setting is set to true. It configures the public file server for tests with Cache-Control for performance.

Under the hood, it includes the ActionDispatch::Static middleware in the middleware stack. This middleware serves static files from disk, if available. If no file is found, it hands off to the main app.

config.eager_load

In the test mode, this setting is typically set to false, to avoid having to load your whole application. However, you might need it while running the tests on continuous integration servers.

config.action_controller.allow_forgery_protection

This disables request forgery protection, which protects you from cross-site request forgery attacks. You don't need this in the test environment.

Production Mode

config.cache_classes

We set this to true in production, as we don't have to reload the code between requests.

config.eager_load

Again, this is set to true as we do want to eager load all the code in memory once the application starts.

config.public_file_server.enabled

By default, this setting is disabled. Typically, your web server (Apache or Nginx) handles this without involving the Rails app. However, you can enable it if that's not the case.

config.assets.compile

The Asset Pipeline precompiles the assets in production. We don't want live compilation on production as it's slow, lacks compression, and will impact the render time of the pages. [source]  

config.log_level

This is set to :info in production to avoid logging too much information. We don't want to accidentally expose personally identifiable information.

config.log_tags

Set to [:request_id] in production to prepend all log lines with the request id.

I could go on, but will just stop here. This is not a complete set of all Rails config settings, but just the important ones that you get out-of-the-box when you generate a new app. For a complete list, check out Rails docs on Configuring Rails Applications.


I hope you found this article useful and that you learned something new.

If you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.

Please subscribe to my blog if you'd like to receive future articles directly in your email. If you're already a subscriber, thank you.