We have talked about the parts of a Rails app running in production. On this page, we describe which tools we are going to use and more importantly - why!?
We will not be covering the installation of any of these tools, just the selection. We will talk about the installation later.
Linux is normally the default OS used for deploying Rails app due to
- Licensing reasons: Linux is free, so one pays only for the hardware cost when hosting.
- Well documented: Processes for running servers and Ruby tools are way more documented for Linux than Windows (or BSD for that matter.)
You can choose a distribution to your liking. For most parts, people go with Ubuntu for the sake of the large community. We are also going to use Ubuntu for the same reason in this guide.
Ruby version manager
You can install Ruby system-wide but if you ever need to change your Ruby version for any reason (for example, if you upgrade ruby and something breaks, you need to revert the ruby version), it can be difficult to do it on a system-wide level.
For this reason, we will use a ruby version manager. There are 3 options available:
Out of these, rbenv is the one which is most reliable one because of how it works (the detailed reason are beyond the scope of this guide). Some people say chruby is better than rbenv but I was not able to get it working properly. In addition, rbenv seems to be working well enough for me. If chruby works, I’d try it out later and maybe update this guide.
That should be obvious - we need to install Ruby for making rails work. We will use rbenv to install the version of ruby that we want.
I happen to use Ruby version
2.5.2 and it works well, so we will try that out.
Ruby on Rails
This guide is for Rails version 5.2. It should work for earlier as well as later versions. There is no particular reason why I am choosing this version - it’s just because this is the latest one and works well.
There are many choices but the two most popular ones are nginx and Apache. Since nginx is getting popular and is generally superior than Apache at handling multiple requests at a time, we will go with nginx. Also, nginx is seemingly easier to configure (at least the configuration files look better) than apache.
Thus, the reason behind choosing nginx is mainly - speed and ease of configuration.
This one was hard because there are actually a few choices:
puma: This guy comes with Rails as a default application server. When you run
rails serverwithout changing the configuration, it would launch puma. It’s fast and slick and runs Rails apps with minimum-to-no configuration.
unicorn: It does not do multithreading, has been popular in the past and is well known.
passenger: Passenger tries to be as easy to configure as possible and can plug itself into nginx and has a great record for concurrent request handling.
I recommend, you read this article to understand the differences.
We are going to go with
puma. While we can just as easily configure passenger, we are choosing puma for one big reason -
puma is the default server for Rails. Also, since the installation is just about installing the Gem, the configuration overhead is practically zero. It’s fast and stable and is feature-rich. However, the biggest of the reason is that it comes with Rails as a default. Let me explain a scenario to back up my argument for ‘default is great’ here.
In most cases, when you develop a Rails app, you would probably not change the application server locally. This is one of the reasons why people stick with default - because the default is known to work. If, for some reason, your app server fails in production and you need to debug it, debugging one on a production site is going to be complicated if your local install is different - you would have no local repo to compare the error messages and lines with. Lack of a local installation would mean you can’t look at the source code on your machine and try to change something on production. So it’s best that you make sure the production server has the same tooling as your development machine, wherever possible.
If you know about the 12 factor app guidelines, one of the points in there is to make sure your local development environment is as close to the production as possible. It is reasons like the above that the guidelines recommend having the same tooling on the server as on the development machine.
You should use another application server (say passenger) only when you have the same application server (in this case, passenger) for your development machine. Thus, puma.
This guide does not care about which database you are using because we will not be covering how to install, start, stop your DB server, or how to connect it to your Rails app. You should know that already (or look for another guide).
There are multitude of tools available for deployment. There are Rails specific tools, there are multi-framework supporting tools which also support other frameworks (even other languages). One of the most popular ones is capistrano. Then there is mina. There are tools which are specific to AWS or Google Cloud and then there is Heroku.
However, all of them have certain drawbacks:
- AWS CodeDeploy: If you are not in the AWS ecosystem, this might not be relevant to you.
- Heroku: The free tier is fine but beyond that, Heroku becomes very costly as you begin to scale.
- Capistrano: You need to learn the DSL to write your tasks.
- Mina: Here too, you need to learn the DSL.
Both capistrano and mina extend the Rake tasks and you need to first understand Rake before you can understand what Capistrano or Mina do.
However, there is one great thing about mina: it actually creates a bash script and then executes it on the remote server. What it does is this:
- It reads your deployment config file.
- It generates a shell script which would deploy your code to the remote server.
- It does SSH to your server and sends in the generated script in one go.
However, once again, the negative part is: you have to learn how Rake works, how to write Rake tasks and how to use the DSL to write Mina’s tasks as well. So there is learning curve with Mina too. Looks like there there are no easy ways!
Our choice of deployment is a ‘script’ (which I initially generated by Mina using the
--simulate option and then altered), is placed on the server and can be invoked remotely. The advantages with this approach are:
- Easy to read and understand what is going on.
- Colorful printing for errors, warnings, notices and prompts.
- If you want to make a part interactive (e.g. letting the user take decision whether to abort the deployment when an inconsistency is found), it’s easy.
- You can place this script (if it is non-interactive) as part of a git-push hook so your deployment is done using
git push origin master.
- Customisation to a large extent without having to understand a DSL.
So our choice here is a custom script and the reason is mostly - easy to understand and edit without having to learn any custom tool for deployment.