How I run Rails apps in production
I saw a post on r/rails recently where people were discussing their preferred Rails stacks, so I figured I’d write about mine. This is pretty generic, so this applies to almost all Rails apps I run but not specific setups or features.
My setup is based on a few core principles.
- Stick to defaults unless there is a big advantage for not doing so.
After being a sysadmin for a few years I learned that with almost all software, sticking to defaults will save pain in the future when upgrading.
- Gems make upgrading more difficult.
I only add a gem when it saves a lot of time. I always make sure that it’s in active development, is used by many people and its dependencies are reasonable.
A big mistake I see many who are new to Rails make is add tons of gems until the Gemfile reads like a short novel.
- Stick to small, simple gems/tools that are easy to understand and use.
This expands a bit on the previous point, but this is for example why I prefer ruby-install + chruby over rbenv/rvm or minitest over RSpec.
I always host my apps on DigitalOcean (referral link). It’s simple and quick to get set up. And if I want to do something more fancy, they have a nice API and support for server images.
I always pick the latest Ubuntu LTS version, it has never caused problems for me. I used to care a lot about which distribution I run on my servers but now I don’t since it doesn’t seem to make a big difference in the grand scheme of things.
Once the server is up I do a few mandatory tasks, most of them are the same stuff as outlined in this blog post about “My first 5 minutes on a server”. I should probably have an Ansible task for this but so far I’m not sure it would have saved time.
Finally, I install the latest Ruby, using ruby-install and chruby.
Rails & Gems
I install the latest Rails that I can use on the project in question. I’ve already covered how I test Rails apps in a previous post. For my development environment I use:
- better_errors: replaces the Rails error page with a more useful one
- binding_of_caller: adds some functionality to better_errors
- dotenv: Load variables from .env file to ENV in Rails
- pry: powerful replacement for irb, also super handy for debugging. I could live with all of the ones above but not pry!
That’s about it. All of these gems are very useful and make debugging a lot easier.
There are always two gems/services that I use on all my projects. The first one is Rollbar which reports any errors that occur in my app. I’ve used Airbrake as well, and there are a few other alternatives too. I don’t think it matters which one you use, but it’s very important you use something to report errors in your app, otherwise they’ll just go unnoticed.
The second one is Papertrail. It’s a service that accepts logs from you app and lets you read them. It’s handy but not absolutely necessary, it’s always possible to connect to the server and read the logs manually. Again, there are alternatives that do the job but I’ve come to like Papertrail’s UI.
I usually add NewRelic, depending on the complexity of the project. Sometimes I don’t bother adding it until there’s a problem that NewRelic can help me fix.
I use puma as the web server with nginx in front of it.
Views & Assets
I’ve used Haml and Slim in the past for my views and while they look nice I prefer ERB. There are a few reasons for that, most importantly that I just prefer to read ERB over other template engines. Similarly how I prefer to read ES5 over CoffeeScript.
Another reason is that almost everyone knows HTML. I’ve worked on a projects in the past where designers had to spend time to get used to Haml to write views. Also if I ever have to outsource some design/markup work, it makes life a lot easier to not have to convert the HTML to Haml/Slim.
It’s possible to argue that it’s quicker to write Haml or Slim compared to HTML but I prefer to let my editor deal with that problem with something like Emmet.
I use a fairly standard Capistrano setup to deploy my apps. It’s pretty simple for the most part and just works.
I’ve used Ansible and Docker on a past project for deployment and learned that using Docker does not add much value unless you run your app on more than one server because there’s no simple way to do zero downtime deployment with Docker. I could go into detail about that but it should probably be its own writeup.
I’ve seen many people run only their Rails app in a Docker container but everything else is outside of Docker. That’s a nice way of actually containing the complexity your app in one place but zero downtime deployment remains an issue.
I have a calendar entry to remind me to update my apps every month. I update Ruby and all the gems and make sure tests pass and everything still works. If there’s a regression, I fix it and deploy. Usually this is a very quick process.
The worst thing you can do is let your app’s gems get really out of date, you’re setting yourself up for a disaster later on when you need to upgrade. I think this applies to almost all package management systems, not just Ruby gems.
There are people who will argue that upgrading for the sake of upgrading is bad and maybe they’re right, but doing things this way has worked well for me. On apps that don’t have thorough tests for example, I’d be cautious about upgrading regularly.
There are seemingly endless ways to compose and run Rails apps out there. This is how I run mine. I’m not saying it’s the best way but it’s what I prefer and lot of this is down to taste. If you think I’m missing something life changing please let me know!