I spent a decent amount of time last month playing around with scripts for setting up a Rails server from scratch. Turns out that the whole process is simpler than what I had to go through 7 years ago when I was learning how to install LAMP from scratch.
Here’s the basic set of commands to install Nginx + Passenger + Ruby 2.1 + PostgreSQL on an Ubuntu 14.04 Server:
$ sudo su # apt-add-repository ppa:brightbox/ruby-ng # apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 561F9B9CAC40B2F7 # vim /etc/apt/sources.list.d/passenger.list # apt-get update # apt-get install git-core build-essential ruby2.1 ruby2.1-dev \ libruby2.1 nodejs nginx-extras passenger postgresql libpq-dev # vim /etc/nginx/nginx.conf # adduser deploy --shell /bin/bash --disabled-password # sudo -u postgres createuser --superuser deploy # sudo -u postgres createdb deploy # gem2.1 install bundler --no-ri --no-rdoc
$ prompt means user,
# means root)
There are a bunch of other stuff that you need to setup like ssh/sshd/firewall and mail/postfix settings but these commands pretty much covers all the software you need to install before you setup a production app. For example, here’s the rest of the commands for setting up Redmine:
# su -l deploy $ wget http://www.redmine.org/releases/redmine-2.6.0.tar.gz $ tar -xf redmine-2.6.0.tar.gz $ cd redmine-2.6.0/ $ vim config/database.yml $ createdb redmine $ bundle install --binstubs bin --path bundle --without development test rmagick $ bin/rake generate_secret_token $ RAILS_ENV=production bin/rake db:migrate $ RAILS_ENV=production bin/rake redmine:load_default_data $ exit # vim /etc/nginx/sites-available/default # service nginx restart
Why install Passenger and Ruby from apt repositories?
It’s much faster than installing from source.
It’s also easier to update via
Why Passenger instead of Nginx + Unicorn/Puma/etc?
Aside from the reasons above, it requires less configuration and it’s good enough for most cases.
The only way I’d consider other setups is if you need rolling restarts or more control over your memory usage.
Why use BrightBox’s repo instead of RVM/rbenv or install from source?
Most production servers only need one version of Ruby installed so I don’t see a reason for using version managers. They might make sense in staging servers for testing multiple versions, but not in prod.
Gem installation isn’t a problem since you’re going to use Bundler anyway i.e. after installing and having
bundle available system-wide, you can have it install gems locally via
--path like in the example above.
So that leaves us with manually installing from source. However, as mentioned above, installing via apt is just more convenient. The BrightBox packages may sometimes not be up-to-date (DevCon is running in preview 2.2 right now), but they tend to update a few weeks after a major release.
Bonus: Why are we using an older version of NodeJS?
We’re not using a more up-to-date apt repository for NodeJS because we’re only using it for asset precompilation.
There’s a gotcha related to this that we’ll talk about later.
Security: why allow root login via ssh?
Just like the “use rvm in prod” advice, it feels like a lot of people go by “
PermitRootLogin no” when my script instead uses “
For newbies, this makes sense: preventing people from logging in as root is a great way to prevent hacking attempts. In reality, we’re not totally preventing remote “root” log-ins – we’re prevent logging in as root because we want our SysAds to log in as a sudo user so all of their actions can be logged.
For simpler systems, it may be better to just allow remote root login as long as public keys are used, hence “
Why is the deploy user not a sudo user?
It’s not really for security reasons. Yes, having a basic pasword-less user does make our apps secure, considering that Passenger automatically switches the user to the owner of the app, but my main reason for not making
deploy a sudo user is because it doesn’t need it.
It doesn’t need sudo to install gems,
bundle install --path installs to a local folder.
It doesn’t need sudo to restart the app server,
touch tmp/restart.txt does that.
It’s just a simple constraint I added to make sure I’m isolating the
deploy user to deployment tasks.
And besides, some of you might notice how insecure
deploy is when it comes to…
Why is the deploy user a superuser for PostgreSQL? Why use Ident/Peer auth instead of a password?
Same reason as root login: personal server, no need for extra security than needed. If the attacker somehow logs in as deploy, the DB is screwed either way.
In other words superuser + peer auth is about as secure as the usual password approach while also easier to setup (by just a few commands LOL).
There’s nothing special about most of the config files edited via vim in the scripts above. The apt source and default nginx site config file would just contain the recommended settings. The database config can just contain three lines: the environment (
production:), the adapter (
adapter: postgresql), and the database name.
As for the main nginx config file, apart from being edited as recommended by the docs, there’s also one big gotcha that will prevent your app from running in Passenger:
If your app is using sprockets and you’re using an external JS runtime like NodeJS, your app may not run because Passenger can’t find a valid JS runtime via execjs.
Reason? Execjs looks for NodeJS with the PATH, and environment variables like PATH are not automatically used by Passenger.
There you have it, my quick guide to Rails server setup.
I could write a part 2 about deployment, but I’m afraid the tl;dr to that would be “just use Mina“.