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:
- About your Rails application
- How to create and use a specific environment
- Figure out the current environment
- Using gems specific to an environment
- Using Rails initializers for configuration
- Common configuration settings in Rails
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:
Out of the box, Rails provides following initializers:
assets.rb
: configures the asset pipeline.content_security_policy.rb
: defines an application-wide content security policy.filter_parameter_logging.rb
: configures the parameters to be filtered from the log file.inflections.rb
: adds new inflection rules.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.