The Rails Stack

This is a quick overview of what the Rails stack looks like in a typical production environment.

Things that aren’t covered

Machine Provisioning

How a machine gets built is a significant variable. Provisioning largely depends on where you are, and what scale your system is at. I.e., whether or not you are on a cloud provider, which one, and what system provisioning tools you use (e.g., chef or puppet). This article assumes a machine is in place, and will not cover how to bring one into existance.

Code Deployment

How application code gets deployed varies as much as provisioning. Quite often, provisioning requirements will dictate how the code is deployed. While deployment is a critical subject, this article only talks about what the stack looks like after deployment. I.e., we will describe here what the target of deployment is.

The Application Server

The application server is responsible for running one or more processes. A few to choose from include Puma, Unicorn and Phusion Passenger. In this article, I will talk specifically about Puma.

Puma

Puma can manage more than one process, and any number of threads per process. It is designed to be performant via parallelism. Using Puma to start multiple processes reduces the burden on the system startup scripts of doing the same.

Puma is easy to configure, and the configuration can be source controlled.

  config
  ├── puma
  │   ├── development.rb
  │   └── production.rb

Since the configuration files are simply ruby code, you can choose to let the environment override anything. Here’s an example of what the config/puma/production.rb file might look like.

workers ENV.fetch('PUMA_WORKERS', 2)
threads ENV.fetch('PUMA_THREADS_MIN', 2), ENV.fetch('PUMA_THREADS_MAX', 8)

More detail information about configuring Puma can be found in the project’s README.

Utilizing the default directory structure for Puma configuration, the startup command will be simple. Just pass Puma the -e <environment> flag, so it knows what configuration file to load.

$ bundle exec puma -e ${RACK_ENV:-development}

NOTE: Multiple Processes and Reverse Proxies

Even when using multiple worker processes with Puma, it will open a single port (or socket, depending on your configuration), and pass the request through to forked child processes. If there is a reverse proxy setup, it’s only necessary to forward the requests to a single location per machine.

Application Server Startup

Typically, the app server is not run manually. Instead a specialized system level process will take care of running it, allowing for proper restarts on failure, or monitoring of resources like memory.

A couple of popular options are monit and upstart.

Starting application server processes has more to do with system provisioning than with the Rails stack. Generally, you will want to ensure processes are started at the appropriate time, that resource usage (e.g., memory and CPU) is monitored and that any unexpected exit from a process is handled properly.

NOTE: Foreman

In some cases it can be useful to manage the startup of your application processes. For example, when there is more than one process to start (e.g., the web server, and a background worker). The Foreman tool provides a simple way to manage this.

Foreman can be especially useful in the development environment, to keep multiple processes running in a single terminal window.

Foreman can also be used to create upstart or init scripts, instead of using it directly to run commands.

All-in-all, Foreman is worth looking at if your application startup is getting complicated.

http://blog.daviddollar.org/2011/05/06/introducing-foreman.html

A Reverse Proxy

In many cases it is common to run a reverse proxy on every application server machine (or virtual machine). Rails applications often include a lot of static assets (images, css, etc.). Something like Apache or Nginx does a much better job of serving requests for these assets directly rather than running the requests through the Rails process.

The public and public/assets directories can be handled immediately by a reverse proxy, and every other request can be passed to the Rails application server.

While running a reverse proxy could be considered best practice, it isn’t always necessary, depending on your situation.

For example, if your application is configured with an asset_host that points at a CDN, then you can get away with serving static assets directly from Rails, since they will then be cached and served by the CDN’s edge network, offloading the vast majority of the requests to them.

Application Configuration

Configuring Rails applications continually changes (and improves) as Rails grows older. Here are a few goals that are worthy of achieving, that should help form a perspective on how configuration should be accomplished.

  • When a new developer checks out the codebase for the first time, they should get a running system with two commands…
$ script/bootstrap
$ ./bin/rails server # or similar, like...
$ foreman start

While the idea behind script/bootstrap is outside the scope of this post, this goal is worth noting as it means the application should run with appropriate defaults set for all configuration values.

  • Configuration should be source-controlled, ideally with your application source (notable exception being credentials).

This means there’s a history of configuration changes that can be inspected over time.

  • Authentication tokens come from the environment, and are never source controlled.

This includes third-party API keys, database passwords, etc.

With these goals in mind, here is a high-level view of configuring a Rails application.

Environment Configuration

In config/environments/ there are several files that configure Rails itself. The Rails Guides covers these more fully.

One important detail about these files is that they should not contain any credentials (e.g., a password for a mail server). Instead, credentials are managed in the config/secrets.yaml file (see below).

The config/environments/production.rb file has a few important details that relate to the production stack. The serve_static_assets and the x_sendfile_header should be set depending on whether or not you setup a reverse proxy on the local machine. The default is to not serve static files (i.e., you are using a reverse proxy).

If instead you want to serve static files through Rails, make these changes…

config.serve_static_assets = true
config.action_dispatch.x_sendfile_header = nil

Secrets

The config/secrets.yml file is a convenient place to store and manage configuration information. It’s an environment sensitive yaml file, that is evaluated with ERB, so you can bring in values set by the system.

production:
  secret_key_base: <%= ENV.fetch('SECRET_KEY_BASE') %>
  mail_server_password: <%= ENV.fetch('MAIL_SERVER_PASSWORD') %>

Note that contrary to what the name of the file implies, you can put any configuration value you want in this file, and they aren’t required to be secret. For example, if you wanted information about a third party API to be kept in one place…

production:
  # ...
  awesome_api_host: api.thirdparty.com
  awesome_api_username: <%= ENV.fetch('AWESOME_API_USERNAME') %>
  awesome_api_password: <%= ENV.fetch('AWESOME_API_PASSWORD') %>

database.yml

The config/database.yml file can be treated the same as the secrets file. In the latest version of Rails, a database configuration can be provided with a URL, making it a single entry. E.g.,

production:
  reconnect: true
  pool: 8 # expecting 8 threads per process
  url: <%= ENV.fetch('DATABASE_URL') %>
  # example value:
  #   postgres://username:password@database.host.com:<port>/database_name
{% endhighlight %}

## Logging

Application logs should be treated as an event stream*. Your production
application server logs should be sent to that event stream, along with
any other data. This can be done through system level things like syslog
or with a third-party provider (e.g., [Papertrail](https://papertrailapp.com/)).

  *see [The Twelve-Factor App](http://12factor.net/)

## Wrap-up

Hopefully this post has improved your understanding of what a production
Rails stack might look like.

Please send me all feedback (see the footer for contact info).
September 3, 2014