Update:

There is an updated version of this guide for ubuntu 12.04 LTS

Original Guide

When I started developing in Rails I found that setting up a Rails development environment is fairly trivial. I had expected no different from setting up a Rails production server. However, having setup a Rails production server over the past few days has thought me that setting up a production environment is a lot more complex than a development environment.

Add to this that although the development process is well documented for new programmers, the production people assume that you are experienced and know what you are doing. Otherwise, why would you be running a production environment in the first place? But, there has to be a first time for everything and with this (and upcoming) guides I hope to make the Rails production life a bit easier.

In this guide I'll show how to setup a Rails production server. In part 2 of this guide I show how to deploy to this server using Capistrano, which will make your deployment process a lot easier. During this setup we'll keep that goal in mind.

The setup

I'm assuming that you already have installed Ubuntu Server with a non-root user (with sudo rights) and that SSH is setup (e.g. like this.) Use sudo, do not use root, because this is a requirement for rvm. You should setup your hosts file for your server. For example, my server has these settings:

# /etc/hosts
127.0.0.1   localhost
12.34.56.78 server.web-l.nl

# /etc/hostname
server.web-l.nl

This should prevent any messages from apache saying that it could not determine a fully qualified domain name (fqdn).

Installing Apache

We'll be installing apache using apt-get. You'll need sudo to do this. While we are installing apache, we might just as well include some packages needed later on.

sudo apt-get install apache2 curl git build-essential zlibc zlib1g-dev zlib1g libcurl4-openssl-dev libssl-dev libopenssl-ruby apache2-prefork-dev libapr1-dev libaprutil1-dev libreadline6 libreadline6-dev

Now redirect your web browser to your servers IP. It should show a welcome page saying: "It works!". Good!

Installing MySQL

Up next is MySQL, our database server. Install MySQL via apt-get, the installer will ask you for a database root password, make sure to pick a strong password.

sudo apt-get install mysql-server

Let's also create a mysql user for your rails app. I'm naming both the database and the user myrailsapp, but you are free to pick any name you feel appropriate. Make sure the replace the password (correcthorsebatterystaple) with something more secure.

create database myrailsapp;
grant all privileges on myrailsapp.* to 'myrailsapp'@'localhost' identified by 'correcthorsebatterystaple'; 
flush privileges;

Installing RVM and Ruby

We'll get to setting up passenger later, but lets first install RVM and a recent ruby. We'll use the curl command provided on the RVM website. Note: This is the first and only time you will use sudo to do something with RVM. After this, a user just needs to be in the RVM group to use rvm. If, for any reason, sudo is needed to do something with rvm, you have to use rvmsudo instead of sudo.

*Note: * The installation script changed location recently, the new command is:

sudo bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer )

This should successfully install RVM in a multi-user install. The next step is to add yourself to the RVM user group. Let's also add a deploy user with RVM access while we're doing this. Add yourself to the deploy group for convenience.
Also set the permissions on the /var/www folder. With these settings every user in the deploy group can modify files in the /var/www folder. Then, log out of your terminal/shell and log back in. (This will update your group permissions)

sudo adduser deploy
sudo adduser deploy rvm
sudo adduser <yourusername> deploy
sudo adduser <yourusername> rvm
sudo chown -R deploy:deploy /var/www
sudo chmod g+w /var/www

Time to install Ruby. You can check the list of available rubies using rvm list known. I have chosen to install ruby 1.9.2-p290, but you could (should?) go for 1.9.3 once a stable release is available. Compilation can take some time, so get a caffeinated beverage of your choice while your server is busy compiling. Then, set the new ruby to be your default ruby and install bundler. (We'll need bundler later)

rvm install 1.9.2 
rvm --default use 1.9.2
gem install bundler

I've had some issues to get openssl and the readline library working with ruby on the beta of Ubuntu Oneiric. One solution is to use rvm pkg install openssl and rvm pkg install readline before installing ruby (you can reinstall ruby using: rvm reinstall 1.9.2. More info can be found at the rvm website.

RVM, Bundler, Rubygems and management

Basically, there are several good ways to manage our gems with respect to Passenger. There also is a wrong way, which is to install all gems in the same rvm gemset on the same server. Good options (in my opinion):

  1. Use a single server for each application. This gives the best separation and redundancy, but it can cost you, especially if you have a lot of low traffic apps.
  2. Using a separate rvm gemset for each application. There are some really good instructions on how to do this in this blog post by Darcy.
  3. Use Bundlers --deployment option to install all gems into the shared/bundle directory of your app. This is what we'll use in this guide, because it does not require any extra setup (unlike option 2).

You ask why I've installed RVM even if I'm going with option 3? Well, this is because rvm makes it easier to install and maintain your desired ruby on Ubuntu. The apt packages for ruby are usually a bit behind.

Installing Passenger

Start by installing passenger. Then install passenger-install-apache2-module using rvmsudo. The module installer is very helpful and will give you directions if anything is missing.

gem install passenger
rvmsudo passenger-install-apache2-module

In the end, the installer will show how to configure the apache configuration files in order to use passenger. Instead of editing the configuration files directly, we'll place 2 files (passenger.load and passenger.conf) in /etc/apache2/mods-available, so that we can use a2enmod and a2dismod to active/deactivate the module. Use sudo to edit these files. The contents below are indicative, use what the passenger installer will return to you. (most likely, the version numbers will change with time)

#/etc/apache2/mods-available/passenger.load
LoadModule passenger_module /usr/rvm/gems/ruby-1.9.2-p290/gems/passenger-3.0.9/ext/apache2/mod_passenger.so

#/etc/apache2/mods-available/passenger.conf
PassengerRoot /usr/local/rvm/gems/ruby-1.9.2-p290/gems/passenger-3.0.9
PassengerRuby /usr/local/rvm/wrappers/ruby-1.9.2-p290/ruby

Activate the passenger module and restart Apache.

sudo a2enmod passenger
sudo service apache2 restart

And voil, you now have Apache running with Passenger 3.

Deploying

In a later guide I'll show how to deploy to your server using Capistrano. Capistrano takes some time to setup, but it is very useful because future deployments can then be performed completely automatic with a single command.

In this section we'll deploy a basic rails app by hand.

  1. Move the default page from /var/www to /var/www/default.
  2. Create and deploy a new rails app.
  3. Configure an Apache Virtual host to access your rails app.

1. Moving the default page

We will move de default apache page into a separate directory to prevent unauthorized access to the directories of your Rails app. Failing to do this would make the internals of your application accessible via http://ip.address/railsapp. This could, for example, provide unauthorized access to database passwords.

mkdir /var/www/default
mv /var/www/index.html /var/www/default/index.html

Now edit the default apache vhost to point to this directory.

# /etc/apache2/sites-available/default
...
DocumentRoot /var/www/default
...
<Directory /var/www/default/>
...
  Options FollowSymLinks # Remove Indexes

2. Deploying a new rails app

We'll scaffold a todo rails app at the server to show the deployment of a rails app. To show how bundler installs gems in production, I'll use a separate gemset that is not accessible by Passenger. This simulates the situation in which you develop on a separate computer. We also run bundle install once to generate a Gemfile.lock that bundler will use when deploying. We'll also add therubyracer gem to our Gemfile, because we need a javascript interpreter later.

Note: Bundler is not really setup to both develop and deploy on the same machine. Although the steps below are informative in showing how to deploy an app, I recommend you develop on a separate machine.

rvm use 1.9.2-p290@rails31 --create
gem install rails
cd /var/www
rails new myrailsapp --skip-bundle -d mysql
cd myrailsapp
rails generate scaffold todo name:string finished:boolean
echo "gem 'therubyracer'" >> Gemfile
bundle install
rvm gemset use global

In the new rails app we have to setup the MySQL database.

# /var/www/myrailsapp/config/database.yml
production:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: myrailsapp
  username: myrailsapp
  password: correcthorsebatterystaple

We'll run bundler as the deploy user to simulate the behavior we'll expect with capistrano in the next guide. Log in as deploy with ssh before continuing. rake assets:precompile requires a javascript interpreter, good thing you added therubyracer to the Gemfile. Touching tmp/restart.txt restarts passenger.

rvm use 1.9.2-p290 --default
bundle install --deployment --without development test
bundle exec rake db:migrate RAILS_ENV=production
bundle exec rake assets:precompile
touch /var/www/myrailsapp/tmp/restart.txt

3. Configure a vhost

The easiest way to check if your rails site is working, is to redirect the default virtual host to your rails app. However, if you have a domain name you could also point the DNS settings to your server and setup a vhost to serve that domain. For simplicity we'll turn off the default vhost and redirect all traffic to our railsapp vhost. Note: the vhost needs to direct to the public folder of your app, everything else is handled by Passenger automagically.

# /etc/apache2/sites-available/myrailsapp
<VirtualHost _default_:80>
  # ServerName www.example.com # Commented out for default
  DocumentRoot /var/www/myrailsapp/public # be sure to point to public
  <Directory /var/www/myrailsapp/public/>
    AllowOverride all
    Options -MultiViews
  </Directory>
</VirtualHost>

Now, activate the vhost by running sudo a2ensite myrailsapp, then deactivate the default vhost by running sudo a2dissite default, and then reload the apache configuration using sudo service apache2 reload and send your browser to http://<serverip>/todos. If all went well, you should see your working Rails application.

Continue to part 2 of this guide to learn how to deploy on this setup with capistrano.

Back to index

Comments:

Bbc6ef69d9d9780501db9ccce53fee85
Marklocklear, about 2 years ago

I could not get this to work. When I go to http://serverip/todos I get "The requested URL /todos was not found on this server." At the root domain or http://serverip I see the "Index of /" page with folders for 'default' and 'myrailsapp'.

Dennis
Dennis, about 2 years ago

Thanks, I just noticed a typo in the vhost configuration where it says `ww` instead of `www`. Can you check if your vhost settings are correct?

Bbc6ef69d9d9780501db9ccce53fee85
Marklocklear, about 2 years ago

Thanks. Couple of things. Here is what I have...

ubuntu@domU-12-31-39-0C-D8-BC:~$ ls -lh /etc/apache2/sites-enabled/
total 0
lrwxrwxrwx 1 root root 26 2012-03-23 13:02 000-default -> ../sites-available/default
lrwxrwxrwx 1 root root 29 2012-03-27 12:13 myrailsapp -> ../sites-available/myrailsapp

So instead of # /etc/apache2/sites-available/default.conf I have # /etc/apache2/sites-available/default. I think that is OK.

I did notice the typo with 'ww' instead of 'www' so that was OK. Running 'sudo a2ensite myrailsapp' did a make a difference. Now when I go to http://serverip I do see the Apache 'It Works' page. However, I am still getting a 'URL not found' when I got to 'http://serverip/todos'.

What should the contents of /etc/apache2/sites-available/default.conf be? In the blog you have two lines with '...' I'm not sure if anything should be on those lines or not?

Also, when I restart apache I get..

ubuntu@domU-12-31-39-0C-D8-BC:~$ sudo service apache2 restart
* Restarting web server apache2 [Tue Mar 27 12:49:01 2012] [error] (EAI 2)Name or service not known: Could not resolve host name *.80 -- ignoring!
[Tue Mar 27 12:49:01 2012] [warn] NameVirtualHost *:80 has no VirtualHosts
... waiting .[Tue Mar 27 12:49:03 2012] [error] (EAI 2)Name or service not known: Could not resolve host name *.80 -- ignoring!
[Tue Mar 27 12:49:03 2012] [warn] NameVirtualHost *:80 has no VirtualHosts

Thanx!

Dennis
Dennis, about 2 years ago

That warning is actually a pretty good indication where I left too much out of the tutorial. I never deactivated the default vhost, so that one caches the request before passenger. Can you do the following:

1. Change the virtualhost config to:
# /etc/apache2/sites-available/myrailsapp

DocumentRoot /var/www/myrailsapp/public

AllowOverride all
Options -MultiViews

(If you have a domain, you should set 'ServerName www.example.com` to listen for that domain btw)
2. disable the default vhost (just for testing without a domain specified, otherwise this one will catch it before it reaches passenger. Once you have a domain going you can enable the default for catching the ip again.)

sudo a2dissite default

3. the enable your rails vhost

sudo a2ensite myrailsapp

4. reload or restart apache

sudo service apache2 reload

5. Try visiting http://serverip (it should show default rails app) and http://serverip/todos

I hope this helps!

Bbc6ef69d9d9780501db9ccce53fee85
Marklocklear, about 2 years ago

I think we are getting close! When I visit http://serverip, I do see the default rails app. However, I get a 404 when I go to http://serverip/todos. Apache error log simply says:

[Tue Mar 27 13:47:51 2012] [error] [client 198.86.53.65] File does not exist: /var/www/myrailsapp/public/todos

Almost like it is looking for the file rather than following the route???

Dennis
Dennis, about 2 years ago

I would expect a rails 404 if passenger is not working. Perhaps passenger is not loaded at all? Can you check:

1. Is the todo route in your routes.rb?

2. can you run `apache2ctl -t -D DUMP_MODULES`? It will list all loaded modules in apache so you can check if passenger is actually being loaded. If it's not loaded, can you check the passenger.conf and passenger.load in /etc/apache2/available-modules and then run `sudo a2enmod passenger` again? (Then restart apache)

3. can you run `rvmsudo passenger-status` to check on any passenger info?

Bbc6ef69d9d9780501db9ccce53fee85
Marklocklear, about 2 years ago

Hey Dennis, thanx so much for the detailed response. Hopefully we will be making this blog post more useful for others!

Looks like I had the wrong page in my /etc/apache2/mods-enabled/passenger.conf file. The path in the file was /usr/local/rvm/gems/ruby-1.9.2-p318/gems/passenger-3.0.9 but I needed /usr/local/rvm/gems/ruby-1.9.2-p318/gems/passenger-3.0.11

Thanks for your patience. I will move on to part 2 now!

67d98ca9936cb2e5a1c436c7db45d5fe
Piffy, over 1 year ago

My problem is really basic:
I'm worjking on my new Raspberry pi toy and after issuing the
grant all privileges on myrailsapp.* to 'myrailsapp'@'localhost' identified by 'correcthorsebatterystaple';
command rvm is installed but not in the execution path. I tried to give the full path to rvm, i.e.
/usr/local/rvm/bin/rvm install 1.9.2
but (unsurprisingly) I got a bunch of

Error running '/usr/local/rvm/scripts/fetch http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p320.tar.bz2', please read /usr/local/rvm/log/ruby-1.9.2-p320/fetch.log
There has been an error fetching the ruby interpreter. Halting the installation.

(btw, fetch.log does not exist).

Any ideas?

Dennis
Dennis, over 1 year ago

Yeah I have a Pi as well, great toy :) runs my VPN server (might warrant another article some day). I updated the guide some time back:

http://www.web-l.nl/posts/21-production-rails-on-ubuntu-12-04-lts

The RVM instructions changed to:

curl -L https://get.rvm.io | sudo bash -s stable

Although I suppose that both instructions retrieve the same RVM config, could you try this one?

You can sign in using Github if you want to comment