If you develop software long enough, you'll eventually have to debug mysterious, environment related issues. I found myself in this particular pickle earlier this week while debugging a show-stopping issue on a staging server. These issues can be substantially mitigated by mirroring your local development stack as closely as possible to your production setup. In my case, the production stack is:

  • Nginx (1.4.x)
  • Passenger 4
  • Ruby 2.0.0p247

Assumptions:

  1. You have a Mac with at least OS X Lion
  2. You are using a ruby version manager (RVM or rbenv)
  3. You have homebrew installed

Install Ruby 2

Using your ruby version manager of choice, install the latest patch level of Ruby 2. With RVM, you'll run something similar to the following:

$ rvm list known
# MRI Rubies
...
[ruby-]1.8.7[-p334]
[ruby-]1.8.7-head
...
[ruby-]2.0.0[-p247]
[ruby-]2.0.0-head
ruby-head
...

$ rvm install ruby-2.0.0
$ rvm use 2.0.0 --default

Install Passenger 4

Phusion Passenger is a web server and application server for Ruby (Rack) and Python (WSGI) apps.

$ sudo gem install passenger

If using RVM, use the following commands to obtain the path to use for the passenger_ruby varibale used in the Nginx config.

$ which passenger-config
/somepath/to/bin/passenger-config

$ /somepath/to/bin/passenger-config --ruby-command
passenger-config was invoked through the following Ruby interpreter:
Command: /Users/username/.rvm/wrappers/ruby-2.0.0-p247/ruby
Version: ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.3.0]
To use in Apache: PassengerRuby /Users/username/.rvm/wrappers/ruby-2.0.0-p247/ruby
To use in Nginx : passenger_ruby /Users/username/.rvm/wrappers/ruby-2.0.0-p247/ruby
To use with Standalone: /Users/username/.rvm/wrappers/ruby-2.0.0-p247/ruby /Users/username/.rvm/gems/ruby-2.0.0-p247/gems/passenger-4.0.14/bin/passenger start

## Notes for RVM users
Do you want to know which command to use for a different Ruby interpreter? 'rvm use' that Ruby interpreter, then re-run 'passenger-config --ruby-command'.

Install Nginx

I used Homebrew to install and compile Nginx with passenger support.

$ brew update
$ brew install nginx --with-passenger

Configure Nginx

Open the nginx.conf file in your favorite text editor. Currently, I'm using SublimeText.

$ subl /usr/local/etc/nginx/nginx.conf

Create a server entry for each app you want Nginx and Passenger to serve locally. The first two server entries are similar to the ones I had to create to solve my particular problem. The last is a more "traditional" entry.

server {
listen 3000;
server_name product.example.local;
root /Users/username/code/product/public;
access_log /Users/username/code/product/log/nginx_access.log;
error_log /Users/username/code/product/log/nginx_error.log;
}

server {
listen 3001;
server_name other-product.example.local;
root /Users/username/code/other-product/public;
access_log /Users/username/code/other-product/log/nginx_access.log;
error_log /Users/username/code/other-product/log/nginx_error.log;
}

server {
listen 80;
server_name blog.local;
root /Users/username/code/blog/public;
access_log /Users/username/code/blog/log/nginx_access.log;
error_log /Users/username/code/blog/log/nginx_error.log;
}

Add Passenger 4 configuration to Nginx. Refer to Phusion Passenger User's Guide, Nginx version for full documentation.

http {
passenger_root /usr/local/opt/passenger;
passenger_ruby /Users/username/.rvm/wrappers/ruby-2.0.0-p247/ruby;

server {
rack_env development; # whatever environment you wish passenger to start in
passenger_enabled on;
}
}

The pertinent bits of the nginx.conf file should look like:

...

http {
passenger_root /usr/local/opt/passenger;
passenger_ruby /Users/username/.rvm/wrappers/ruby-2.0.0-p247/ruby;

server {
rack_env development;
listen 3000;
server_name product.example.local;
root /Users/username/code/product/public;
access_log /Users/username/code/product/log/nginx_access.log;
error_log /Users/username/code/product/log/nginx_error.log;
passenger_enabled on;
}

server {
rack_env development;
listen 3001;
server_name other-product.example.local;
root /Users/username/code/other-product/public;
access_log /Users/username/code/other-product/log/nginx_access.log;
error_log /Users/username/code/other-product/log/nginx_error.log;
passenger_enabled on;
}

server {
rack_env development;
listen 80;
server_name blog.local;
root /Users/username/code/blog/public;
access_log /Users/username/code/blog/log/nginx_access.log;
error_log /Users/username/code/blog/log/nginx_error.log;
passenger_enabled on;
}
}

Update your Hosts file

Open /etc/hosts in your text editor.

$ subl /etc/hosts

Map each server_name to localhost. It should look similar to:

127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
fe80::1%lo0 localhost

...

# My Nginx local aliases
127.0.0.1 product.example.local
127.0.0.1 other-product.example.local
127.0.0.1 blog.local

Save and close the file (you will likely need to enter your password).

After all the configuration changes are complete, simply stop and restart the Nginx server.

$ sudo nginx -s stop
$ sudo nginx

The three sites should be available at the following urls respectively:

  1. http://product.example.local:3000
  2. http://other-product.example.local:3001
  3. http://blog.local

Summary

You can now serve your rails projects locally using Nginx, Passenger 4, and Ruby 2 on Mac OS X.

I hope you found this useful!