Skip to content

Migrating Rails Apps to VPS - worth it?

Arit Amana on December 05, 2018

UPDATE: I decided to leave my apps on Heroku, thanks to a convo I had with @zspencer . I've reproduced our conversation below - hope it helps someo... [Read Full]
markdown guide

Hey Arit!

I always get nervous when migrating off of a platform such as Heroku and onto ones own Virtual Machines. I help clients do it all the time, but before we proceed I make sure I really understand why they're doing it. Heroku does an amazing job of encapsulating a lot of the costs of hosting an app, so much so that it becomes easy to take what it does for granted. Hosting an app with any significant traffic on a self-maanged virtual machine will likely result in someone needing to respond to an issue in off-hours; which means ~12x more bad-days per year.

You will also now need a database provider with a backup and recovery strategy; so that you don't lose all that user data if the virtual machine the database is running on falls over. Oh, and if your traffic spikes and you need a bigger machine right now? Well, hope-to-god your investment in infrastructure automation wasn't neglected so you can spin up a replacement web or database server within a few minutes; like you can on Heroku.

This is compounded by the fact that most Virtual machine providers do not guarantee individual machines up-time. The expectation is that you will have multiple machines operating behind a load balancer, so that if an instance falls over, the other instance will stay up.

That said, it would be 100% shitty if I second-guessed your question and moonwalked away; so I'll try and provide the help you asked for and not the help you didn't.

To understand how to go about solving the problem, I'm going to restate the question and unpack the assumptions I'm making.

How do I go about hosting multiple Rails applications on a virtual machine, then directly requests to the correct application based upon which sub-domain the request is coming from?

There are two main problems to solve:

  1. How do I configure, deploy and run multiple rails applications on a single virtual machine?
  2. How do I route traffic from sub-domains to different rails applications on a single server?

I'll start with problem 1 and then go into problem 2.

> How do I configure, deploy and run multiple rails applications on a single virtual machine?

First, a story. Because I'm old and a little high. A long, long time ago I mostly wrote PHP code. The really cool thing about PHP is that the when you write a PHP program, you can literally copy and paste the files from your personal computer into a folder on the server and the program is ran by the server. Just like that! No re-booting, no "dynos deploying." This worked because the PHP language's interpreter is ran inside the process of the Apache web server itself as an extension.

Each PHP file was, in effect, it's own application and each HTTP request would run that particular application from start to finish. This meant only one process, Apache, was listening to port 80 and 443 for HTTP and HTTPS requests and taking responsibility for choosing which program would handle it.

That's what Phusion/Passenger does - It runs as many Ruby/Rails applications as you want within a single(ish) process. Nginx winds up doing the heavy lifting of the http stuff, while Phusion/Passenger runs the Ruby code and makes sure each application is as isolated as it can be.

Puma, however, follows a different process model. Puma expects that it will be running a single application across many processes. Puma then exposes a single Rack-compliant application (In your case, a Rails app) on a single network port to provide access to that application. You would then run Nginx and configure it to direct traffic that matches a particular pattern to that particular port.

The pros and cons of each solution, single-process, many apps or single-app, many processes is not obvious. Which is part of why people complain "Rails doesn't scale" or "Rails doesn't perform." The single-process, many applications solution is perfect for environments where you "scale vertically." Scaling vertically is when you increase the disk, processor and memory performance of a server to better support high traffic. This is perfect for processes that may take a while to start, or if each request has a lot of work to do before it can safely be returned to the client. If you were to have many processes handling requests, you would likely see the server CPU "thrashing" with a bunch of processes fighting to use up all 100% of the processor power and failing because there's really only 2~4 "real" processors on the virtual machine.

Single-app, many process designs such as Puma + Nginx are well suited to situations where individual response time must be incredibly low, and a lot of the work can be delegated until after the response is sent to the HTTP client. Each request may take very little processing power to execute upon, so having a number of processes handling each request is unlikely to result in a lot of thrashing.

To be honest, picking the "right" scaling model becomes one of those really good problems to have! The nice thing about Rails applications is it's possible to use either or, and switch based upon how your application winds up being used. Here's two guides from linode on running Nginx with Rails:

I apologize for mixing and matching linux distributions, but I couldn't find good instructions for different technologies on matching distros :(.

OK, on to question two!

> How do I direct traffic to the appropriate application based upon the sub-domain?

Story time again! Apache and PHP allowed you to direct traffic to the appropriate application using mod-rewrite Someone coming to Host up the PHP files in ~/site/accounts! Done! Nginx also provides a mechanism for this using their block configuration. There are a number of really powerful ways to make routing decisions and I can't walk you through each of them, but the one you'll want to look out for to do sub-domain level direction to different applications is server_name; and the one you want to look out for to point it at which application on which port is proxy_pass

I am super lazy and tend to reference this full example of the nginx configuration options when I'm trying to figure out my nginx configuration.

THere is a third, secret, unasked question which is:

> How do I keep track of all this stuff so I can spin the server back up if it goes down?

The answer to that is there is an entire swathe of tools designed for storing your server configuration and then using that configuration to re-create servers. I personally prefer ansible but I haven't had to do server management in a very long time, so the cool kids may be using something else nowadays. Back in the day I used Capistrano which still looks like it's maintained and is designed for Ruby people.

Good luck! Hopefully this wasn't too rambly and if you have further questions I'll do my best to reply more specifically.


Wow Zee! THANK YOU! I owe you a coffee :) Please look for a direct message for me, where I share more about my particular situation. I'm so grateful.

Note: I think you need to follow me to allow me direct-message you :)


Hmm, not sure how direct messages work on here, but if you go to my twitter or github you'll be able to find my email, and my twitter DMs are always open.

If you follow one another you can DM.

Open DMs coming at some point.

And yeah, amazing response.


You may want to checkout hatchbox if you're not trying to do this configuration on your own. Chris has done an amazing job with it and has a ton of great information on guides on his site GoRails. He was actually just talking about the difference between Puma and Passenger.

Which should I use? Passenger or Puma?

First, let's take a quick rundown of the difference between the two.

Passenger is an NGINX module that manages and runs your Rails apps. It installs alongside NGINX which means you don't have to manage a separate process for it. If NGINX is running, then Passenger is running.

Puma, on the other hand, is a separate process. It's going to run your Rails app, but you'll have to setup some scripts to manage the service manually and make sure that it gets restarted when it crashes, etc.

Passenger has an open source version as well as a enterprise version you can pay for. Puma is completely free and open source.

I'm sure you're thinking: "Great, but that doesn't help me decide!"

Here's a quick pros and cons list of Puma vs Passenger.


  • Super-fast and managed seamlessly with NGINX
  • Well documented and easy to install
  • Easy debugging when app is broken
  • Supports Python and Node apps too
  • Multi-threading and other features are expensive

And now for Puma...


  • Completely free and open source
  • Supports both multi-process and multi-threading
  • Comes with Rails now by default
  • Manual setup, and process management required
  • Harder to debug when your app is broken

Passenger also wrote up a comparison between the two that goes into more detail. You can find that here.

Here's the thing:

Puma can be really tricky to learn if you're a first-timer deploying Rails to production. You have to learn SystemD, how process management works, and a fair bit about Linux to get it working reliably. It isn't going to hold your hand if you deploy broken code. Your app will just stop responding and you'll have to know where to look for the logs to figure out what's going wrong.

Passenger's pretty good at alerting you when something goes wrong. It'll display an error page and tell you to check the logs immediately. The direct integration with NGINX makes it easier to work with too as you don't have to learn anything about SystemD to get it running.

For beginners, I always recommend Passenger.
Since Passenger is so easy to use and friendly when errors occur, it's what I recommend to most people. If you're new to running Rails in production on your own servers, it's an easy recommendation.

Unless your site has massive traffic, you'll get along well on Passenger's free version. It's what I use on every day and serves up 2M pageviews a year without breaking a sweat.

And just a quick reminder, you can always change this later on. Just pick one and get your app in production. 👍

Either way you go passenger or puma, self-hosted or using hatchbox, definitely report back on your findings!


Hi Drew! I decided to stay on Heroku lol. I've updated my blog post to reflect this decision. Thanks for your thorough response - I feel so supported 🤗


Absolutely! I remember when we moved from AWS to DigitalOcean. It was super cool to be setting up my own servers but also super scary to being configuring from scratch. First one I did took me a whole day, but I can spin a new one up and have an app deployed in under an hour now. My suggestion is no matter if you're doing it barebones or using a service like Heroku take notes!


Can't tell you much about running rails apps in parallel, but I'd guess that's probably the easier part anyway. If you're ok with using nginx, you can easily configure 5 server blocks, each with their own server_name (the subdomain) that reverse proxies to the rails instance either through localhost or unix domain sockets (no idea if rails supports those). From there you can use nginx to gzip data, handle https for all of your apps in one place, do some caching, take care of server-push, etc. Overall a good option if you want scalability. If you're feeling adventurous, I'd suggest going with openresty instead of vanilla nginx; the added benefit of lua scripting comes with almost no cost and may allow for some easier customization.


Thank you DarkWiiPlayer, I'm grateful :)

code of conduct - report abuse