Overview
When it comes time to deploy your Rails app to a production environment, there are myriad options available. This article will explain the nginx/unicorn combo, as deployed on Ubuntu linux. After reading this article, not only will you be able to set up your own server with the unicorn Rails server and the nginx web server, but you’ll also understand many of the available configuration options so that you can adjust your server setup to fit your application properly. While I won’t get into the details of comparing the nginx/unicorn combo with other options, I will give an overview of what each component provides and why it is a solid choice for your production server environment.
Unicorn
In this article, I’ll be using unicorn as the Rails server. There are definitely other options out there. Rails servers such as puma and thin are quite similar and have their own benefits. I’m going to use unicorn because it is a tried and tested tool that can also teach the basics of similar Rails servers.
Unicorn is a “Rack HTTP server for fast clients and Unix”1. Let’s dissect that statement for a moment.
First, it’s a Rack HTTP server. This means that it implements the HTTP protocol for Rack applications. I won’t go into the HTTP protocol, but you can read more about it on wikipedia. Rack is a gem that provides a basic interface for ruby applications to respond to web requests. Basically, rack applications must respond with an array of three elements: an HTTP response code, a hash of headers, and the response body that responds to an each
block. Modern versions of Rails run on Rack, which enables servers like unicorn, thin, and puma to work with them directly.
Next, unicorn is a server for fast clients and Unix. What is a fast client? A fast client is another application or server that can interface with the Rack server quickly, without much latency. Unicorn is not good as a standalone server: it wasn’t designed to handle slow requests that occur over network connections. It relies on Nginx or Apache to handle the buffering and queuing of web requests so that it doesn’t have to worry about concurrency and event-driven programming2. Unicorn is basically the glue between nginx and rack, where Nginx is the fast client.
Unicorn has some features, while not necessarily unique to it, that make it ideal for a Rails server. One such feature that comes in handy is the ability to deploy changes to your application without causing downtime. The problem of zero-downtime deployment is often more complex than a simple unicorn no-downtime reload, but for the most part it is all you need. Unicorn is made to handle high-CPU loads, and is usually configured so that there are the same number of worker processes as CPUs.
Nginx
From Nginx’s website, “Nginx [engine x] is an HTTP and reverse proxy server, as well as a mail proxy server…”3 We will be using nginx as a reverse proxy server and as a regular HTTP server. It will serve requests to our Rails HTTP server (unicorn) and deliver assets (images, stylesheets, and javascript files) over HTTP. A couple of things that are great about nginx are that it can handle a lot of concurrent requests in parallel, it’s quite easy to configure compared to its competition, and it has a relatively low memory footprint.
The following sections explain, in a fair amount of detail, how to set up your server with unicorn and nginx.
Prerequisites
This tutorial was written under the assumption that the server used is Ubuntu linux, release 12.04 or 14.04. You will need root or sudo access to the server to complete the setup properly. You should also be familiar with the basics of git and the linux command line.
Step 1: Set up unicorn
To set up your server correctly, you’ll first need to set up your application’s code base. If you’re using bundler (and you should be if your app is written in Ruby), then all you need to do is add the unicorn gem to your Gemfile:
1 2 |
|
Then run bundle install
, or just bundle
for short, from your application’s
directory:
1 2 |
|
One more recommendation I’d like to make is that you use binstubs. You can set your application up with binstubs by running bundle --binstubs
. This will create a bin/
directory in your application root with all of the executables for the gems in your Gemfile. Having binstubs will make development easier: you can set your PATH
by add the following line to your ~/.bashrc
or ~/.bash_profile
:
1
|
|
Then, instead of always prefixing your commands with bundle exec
, you can type just the command (e.g. rake
instead of bundle exec rake
). If you don’t want to change your bash’s PATH
variable, you can still use bin/rake
, bin/rails
, bin/unicorn
and the like. This will also become handy when configuring your server’s unicorn init script, as you’ll see later in this article.
Configuring config/unicorn.rb
The unicorn command accepts a configuration file as one of its options. The defaults are pretty sane, but you’ll still need to configure a few things to match your application’s setup. The unicorn config file is written in Ruby and is usually located at config/unicorn.rb
within your application’s root folder.
First, it’s helpful to set a variable to your application’s root folder:
1 2 3 |
|
Next, we’ll want to configure the number of worker processes. Unicorn always runs with one master process that can terminate and start one or more worker processes. A good guideline on how many worker processes to set is to match the number of CPU cores your server has. I like to set a different default for production, but you can do whatever meets your needs here:
1 2 3 |
|
Unicorn can listen on ports and/or sockets. I prefer listening on a socket to remind myself that unicorn works best with fast (non-networked) clients. You can set each worker to listen on a separate port in the after_fork
block below for testing.
1 2 3 4 5 6 7 8 |
|
The default unicorn timeout is 60 seconds, so if your application needs a longer timeout (for generating reports or the like), make sure you set a reasonable timeout here:
1 2 3 4 |
|
Use unicorn’s DSL (Domain-Specific Language) to set the working directory, pid file, and standard in and standard out. We’ll be using the pid file location later on, so take note of how you set it here.
1 2 3 4 5 6 7 8 9 10 11 |
|
Finish up the file with some Rails-friendly settings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
The unicorn repository has a solid example for your reference: unicorn.conf.rb.4
Test that your unicorn configuration works by running the following from your app’s root directory:
1 2 3 |
|
Use Ctrl-C to exit the process.
Commit and push your changes (Gemfile
, Gemfile.lock
, config/unicorn.rb
, and bin/*
) and move on to the next step.
Step 2: Set up Ruby, Git, and Your Database
I won’t beat a dead horse here. There’s already a very nice blog post on how to set up Ruby on your server using rbenv. You’ll also want to set up git (also in that article), and your preferred database. Postgresql setup is explained in that article, but you could just as easily set up MySQL using the following command:
1
|
|
Step 3: Set up Unicorn Init Script
You’ve already set up your application to work with unicorn, so now set up your server to work with unicorn as well. Unicorn should start automatically when your server (re)boots so that you don’t have to worry about that every time your server restarts. An init script can make managing your unicorn processes much easier. In this section, I will show you how to set up an init script for your unicorn process.
Configuring /etc/init.d/unicorn
The unicorn init script should be located in the /etc/init.d
folder, and is usually called unicorn
. With such a setup on an Ubuntu server, you’ll be able to run commands like sudo service unicorn upgrade
or sudo service unicorn status
.
The init script is written in bash, and should start with an INIT INFO
section5:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The above section tells your server (when we add unicorn
to the startup scripts) that the $local_fs
, $remote_fs
, $network
, and $syslog
boot facilities must be available before this script can start. Likewise, Required-Stop
indicates that this script should be stopped before those facilities to prevent conflicts. It also sets some default start and stop init levels, a short description and a multiline description. You probably shouldn’t touch much in the above settings.
In the next part, you’ll set some variables to help get your app started properly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Once you get the variables set up properly, you should be able to copy and paste the rest of the init script. Note the comments for more information on how things work and why.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
|
The above script doesn’t include all the possible options for signaling the unicorn master process. See the unicorn signal documentation for more possibilities.
Make sure your init script has permission to be executed:
1
|
|
Now, you can run sudo service unicorn start
to start your unicorn process. Check your logs (whatever you set stderr_path
and stdout_path
to in unicorn.rb
) to troubleshoot issues. When you’ve updated your code base, you can run sudo service unicorn upgrade
to gracefully phase in new workers. To make sure unicorn starts on reboot, update the rc.d
startup configuration using the following command:
1
|
|
This will add a symlink to the unicorn init script in the appropriate /etc/rc.d
folders, which are used to determine what starts when.
Using unicorn with RVM or rbenv
The above init script works with rbenv, but you can use RVM just as well. The main difference, from my experience, is that you won’t have to set all the extra RBENV_
variables like I have above. In fact, the following should be sufficent:
1 2 3 4 5 6 7 8 9 |
|
Step 4: Nginx configuration
You should now have unicorn properly configured to serve up your application. To expose your application to the world, you’ll now have to configure and start Nginx.
Installing nginx
Installing nginx on ubuntu is very simple. Use apt-get
to install a stable version:
1
|
|
If you need the most recent stable nginx build, you can use the nginx download documentation to configure your server.
Configuring /etc/nginx/nginx.conf
The nginx install process on ubuntu creates a folder at /etc/nginx
. In this folder you’ll find a few configuration files, along with a sites-available
and a sites-enabled
folder. The sites-available
folder is where you’ll configure your application-specific nginx setup, and /etc/nginx/nginx.conf
is most likely where you’ll configure global nginx settings. The following is a sensibly configured nginx.conf
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
|
As noted in the inline comments, make sure the path to the unicorn socket matches what you set up in config/unicorn.rb
.
Configuring /etc/nginx/sites-available/sitename
This is the final step in setting up your server: your application-specific nginx configuration. The following file shows the basics of setting things up.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
To enable the site you just configured, all you have to do is add a symlink to the configured file into the sites-enabled
folder, then reload nginx.
1 2 3 4 5 6 7 8 9 |
|
Make sure all your processes are running:
1 2 |
|
And you’re done!
Conclusion
You should now have a good idea of how to set up your production Rails server to run on unicorn and nginx. I’ll list the steps here for your reference:
- Prepare your Rails app:
- Add the unicorn gem to your Gemfile.
- Configure unicorn for your Rails app by adding and modifying
config/unicorn.rb
- Install ruby and other dependencies on your server.
- Configure the unicorn init script on your server (
/etc/init.d/unicorn
). - Install and configure nginx (
/etc/nginx/nginx.conf
and/etc/nginx/sites-available/sitename
). - Confirm everything is running, and you’re done!