Phusion Passenger & running multiple Ruby versions
UPDATE February 27 2013: this article has been obsolete. Phusion Passenger supports multiple Ruby interpreters as of version 4.0.0. The PassengerRuby
config option has been made a per-virtual host option, so you can customize your Ruby interpreter on a per-application basis.
One of the questions we’ve been getting a lot lately is whether it’s possible to run multiple Ruby versions with Phusion Passenger, e.g. have app A and B run on Ruby 1.8.7 while having app C run on Ruby 1.9.2. In previous versions of Phusion Passenger there were ways to get around that, e.g. by mixing in Mongrels. As of Phusion Passenger 3 you can run all components as Phusion Passenger.
The setup that we currently recommend is to combine Phusion Passenger for Apache or Phusion Passenger for Nginx, with Phusion Passenger Standalone. One must first identify the Ruby version that you use most. One then proceeds with setting up Phusion Passenger for Apache or Phusion Passenger for Nginx to use that Ruby version. All applications that are to use a different Ruby version can be served separately through Phusion Passenger Standalone and hook into the main web server via a reverse proxy configuration.
Example
Suppose that you have four websites:
- foo.com, to run on Ruby 1.8.7.
- bar.com, to run on Ruby 1.8.7.
- fries.com, to run on Ruby 1.9.1.
- hamburgers.com, to run on Ruby 1.9.2.
And suppose that you’re using RVM to manage these Rubies.
Setting up foo.com and bar.com (Ruby 1.8.7)
The Ruby version that you use most is Ruby 1.8.7, so you setup Apache or Nginx to use Ruby 1.8.7 and to serve foo.com and bar.com.
rvm use 1.8.7 gem install passenger --pre # Then one of: passenger-install-apache2-module passenger-install-nginx-module
# Partial Apache configuration PassengerRuby /home/someuser/.rvm/wrappers/ruby-1.8.7/ruby <VirtualHost *:80> ServerName www.foo.com DocumentRoot /webapps/foo.com/public </VirtualHost> <VirtualHost *:80> ServerName www.bar.com DocumentRoot /webapps/bar.com/public </VirtualHost>
# Partial Nginx configuration passenger_ruby /home/someuser/.rvm/wrappers/ruby-1.8.7/ruby server { listen 80; server_name www.foo.com; root /webapps/foo.com/public; passenger_enabled on; } server { listen 80; server_name www.bar.com; root /webapps/bar.com/public; passenger_enabled on; }
foo.com and bar.com have now been deployed on Phusion Passenger for Apache or Phusion Passenger for Nginx, and running on Ruby 1.8.7.
Setting up fries.com (Ruby 1.9.1)
The next step is to start fries.com in Phusion Passenger Standalone using Ruby 1.9.1. Since port 80 is already used by Apache or Nginx, we use start Phusion Passenger Standalone on a different port.
rvm use 1.9.1 gem install passenger --pre cd /webapps/fries.com passenger start -a 127.0.0.1 -p 3000 -d
Fries.com is now running on localhost port 3000 as a background daemon. Next, connect it to Apache or Nginx via a reverse proxy.
# Partial Apache configuration <VirtualHost *:80> ServerName www.fries.com DocumentRoot /webapps/fries.com/public PassengerEnabled off ProxyPass / http://127.0.0.1:3000 ProxyPassReverse / http://127.0.0.1:3000 </VirtualHost>
# Partial Nginx configuration server { listen 80; server_name www.fries.com; root /webapps/fries.com/public; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; } }
Setting up hamburgers.com (Ruby 1.9.2)
We do the same thing for hamburgers.com. Port 3000 is already in use, so we assign it to port 3001.
rvm use 1.9.2 gem install passenger --pre cd /webapps/hamburgers.com passenger start -a 127.0.0.1 -p 3001 -d
Then we hook it up to the web server via reverse proxying.
# Partial Apache configuration <VirtualHost *:80> ServerName www.hamburgers.com DocumentRoot /webapps/hamburgers.com/public PassengerEnabled off ProxyPass / http://127.0.0.1:3001 ProxyPassReverse / http://127.0.0.1:3001 </VirtualHost>
# Partial Nginx configuration server { listen 80; server_name www.hamburgers.com; root /webapps/hamburgers.com/public; location / { proxy_pass http://127.0.0.1:3001; proxy_set_header Host $host; } }
Performance tip
Phusion Passenger Standalone also supports listening on a Unix domain socket instead of a TCP socket. Unix domain sockets are significantly faster than TCP sockets.
Only Nginx supports reverse proxying to Unix domain sockets; Apache does not support this.
In order to make Phusion Passenger Standalone listen on a Unix domain socket, you need to run it with Nginx 0.8.21 or higher. In fact we contributed support for Unix domain sockets to Nginx specifically for this feature!
Start Phusion Passenger Standalone like this:
passenger start --socket /tmp/fries.com.socket -d --nginx-version 0.8.50
The --socket
option tells Phusion Passenger to bind to the given Unix domain socket. The --nginx-version
option tells Phusion Passenger Standalone to use Nginx 0.8; 0.7 is the default.
Next you must setup an Nginx upstream block with the Unix domain socket as the only entry. Then setup Nginx to reverse proxy to the created upstream block.
upstream fries_upstream { server unix:/tmp/fries.com.socket; } server { listen 80; server_name www.fries.com; root /webapps/fries.com/public; location / { proxy_pass http://fries_upstream; proxy_set_header Host $host; } }
It should be noted that Phusion Passenger for Apache and Phusion Passenger for Nginx already use Unix domain sockets internally for optimal performance. In fact we’ve done this since version 1.0. We plan on elaborating more about our internal technologies in a future blog post.
Conclusion
Those of you who are familiar with Mongrel and Thin will see the similarity. Indeed, Phusion Passenger Standalone was designed to be able to used in a reverse proxy environment such as the one demonstrated in this article. Unlike Mongrel and Thin clusters however you only need a single Phusion Passenger Standalone instance per web application and thus only a single address to proxy to. Phusion Passenger Standalone will take care of starting and stopping application processes for you and will make sure processes are restarted when they crash.