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).