How-To Setup a Linux Server for Ruby on Rails - with Phusion Passenger and GitHub
Honolulu, HI: March 4th, 2009
By Matthew Moore
This document serves as a comprehensive how-to to setup and deploy your typical Ruby on Rails application on a blank new *nix (Unix, Linux, etc.) server. Rails deployment is hard, but it shouldn’t be. This how-to assumes you’ll want to use Phusion Passenger to serve your Ruby on Rails application, and that your application’s source code repository is in GitHub. Most of the defaults are targeted towards those using Ubuntu Linux on their server, but the same setup should be able to be applied to ’nix systems.
Much of this setup has been stolen from the wonderful articles on Slicehost (articles.slicehost.com ), and some tweaks from various forums around the web. It’s our goal to get this into one place, and in the correct order so you don’t have to follow a million links, and you can be sure you haven’t missed any steps.
We’d like to keep it as comprehensive as possible, and actually use it as a reference guide internally at ThriveSmart Please feel free to add comments with any clarifications or additions. We’ll do our best to keep this updated.
Table of Contents
- General Server Setup
- Setup a Non-Root User with SSH & Sudo Access
- Secure Access to Server Ports
- Base Software Installs
- Web Application Software Setup: Apache, Rails, MySQL, and Git
- Apache Basic Install
- Apache Deployment Directory Permissions
- Ruby Basic Install
- Rails Installation
- MySQL Installation
- Git Installation
- Postfix Install
- Phusion Passenger Setup
- Web App Deployment
- Turning your Web App into a Git Repository
- Setting up Capistrano to deploy to your server from your git repository
- Setup Shared YML Files
- Setup Your App’s Database
- Deploy The First Time!
- Setup Passenger vhost
- Optional Sub-URI / Sub Directory Deployment
- SSL Certificate Installation
- Optional Extra Installs
- Final Notes
General Server Setup
Setup a Non-Root User with SSH & Sudo Access
Usually, you’ll want to change the root password for whatever server you’re using. Just run:
passwd
You’ll then want to setup another user, usually for deployment. For the sake of this document, the user will always be called “app”
adduser app
Give the user sudo access.
visudo
At the end of the file add:
app ALL=(ALL) ALL
Give the user passwordless SSH access from their development box. You’ll first need to make sure the user has an SSH key on their development box.
LOCAL$ ls -al ~/.ssh
Make sure you see the public key file: id_rsa.pub. If you don’t, you’ll need to run ssh-keygen -t rsa. There are great tutorials for getting your own SSH key on your development box. Assuming you do have this .pub file, you can copy it to your server, so the server knows to let you in without a password. SCP this public key file with:
LOCAL$ scp ~/.ssh/id_rsa.pub app@123.45.67.890:/home/app/
To configure put this public key on the right place on the server, run:
mkdir /home/app/.ssh mv /home/app/id_rsa.pub /home/app/.ssh/authorized_keys chown -R app:app /home/app/.ssh chmod 700 /home/app/.ssh chmod 600 /home/app/.ssh/authorized_keys
The app user now how has passwordless SSH access. Let’s go and make connections to the server a little more secure.
Secure Access to Server Ports
First, we can restrict SSH access a bit.
nano /etc/ssh/sshd_config
Use can use this ssh configuration as an example: ssh_config.txt
The main things to change (or check) are:
Port 30000 --- change to a port of your choosing Protocol 2 PermitRootLogin no PasswordAuthentication no --- If you don't need to login from anything but your dev. box. X11Forwarding no UsePAM no UseDNS no AllowUsers app
Now, we can restrict access to different ports of the box. We can do so by editing the IP Tables. Let’s make a test file first, and then move it to the right place.
nano /etc/iptables.test.rules
Here’s a good example: iptables.txt
Just be sure to change the port 30000 in the sshd_config to the port you chose.
To install the iptable file you made, run through these commands:
iptables-restore < /etc/iptables.test.rules iptables -L iptables-save > /etc/iptables.up.rules
This saves your rules in a file so they can be reloaded on a reboot. Let’s make sure they’re reloaded by:
nano /etc/network/interfaces
Add a single line (shown below) just after ‘iface lo inet loopback’ to restore them on a reboot:
... auto lo iface lo inet loopback pre-up iptables-restore < /etc/iptables.up.rules ---- This line # The primary network interface ...
To make sure all the new changes are running, reload SSH rules.
/etc/init.d/ssh reload
BEFORE you do anything else, open a new terminal from you development box, and try logging in. We want to make sure the port restrictions, and ssh rules were setup correctly, before you logout of root and can’t modify them anymore.
LOCAL$ ssh -p 30000 app@123.45.67.890 sudo ls
If all goes well, you’ve logged in as app, and can run sudo. If so, you’re ready to logout of your root SSH terminal, and begin working solely in your app terminal!
Base Software Installs
First, set the locale correctly.
sudo locale-gen en_US.UTF-8 sudo /usr/sbin/update-locale LANG=en_US.UTF-8
To make sure you’re completely current with your base OS install, run:
sudo aptitude update sudo aptitude safe-upgrade sudo aptitude full-upgrade
Also, be sure to get gcc and make installed, and make sure you ‘Y’ to all the dependences it asks you to install:
sudo aptitude install build-essential
Web Application Software Setup: Apache, Rails, MySQL, and Git
Since we’re going to be running Phusion Passenger and GitHub, there’s some software all of us have to get installed: Apache, Rails, MySQL, and Git. Thankfully, they’re not hard.
Apache Basic Install
Let’s install the base:
sudo aptitude install apache2 apache2.2-common apache2-mpm-prefork apache2-utils libexpat1 ssl-cert
You’ll get a warning which we can fix. (apache2: Could not reliably determine the server's fully qualified domain name,
using 127.0.0.1 for ServerName)
Open the main apache config:
sudo nano /etc/apache2/apache2.conf
At the bottom of the file add the following:
ServerName slicename
Change the ServerName to your Slice hostname or a FQDN (like thrivesmart.com. ; this Slice has a hostname of ‘slicename’).
Once done, save apache2.conf and gracefully restart Apache (this method of restarting won’t kill open connections):
sudo apache2ctl graceful
Apache is now running correctly. In a browser, make sure of this by going to http://123.45.67.890. You should see the default Apache page saying It works! – and so it does.
Apache Deployment Directory Permissions
There are 3 basic users that will be accessing files in your web directories. root, your slice user (app), and finally, the Apache user. On Debian based systems it is usual for Apache to be ‘www-data’ and be in the ‘www-data’ group. Other Linux distributions use the user ‘nobody’ or even ‘apache’. For this document, it’s www-data since we’re on ubuntu — but you can replace the www-data with the correct user for your OS in the following commands.
First, make sure your slice user (app) is in the www-data group:
sudo usermod -a -G www-data app
Unfortunately, you’ll have to logout, and log back in to see this take effect. Afterwards, run the groups command to make sure you see app user’s new group (www-data):
groups
You’ll then want to make sure that all files in the apache directory structure we’re going to use has the www-data group:
sudo chgrp -R www-data /var/www
To make sure any new files that are created in the /var/www directory inherit this setting (sticky or stickiness), we’ll also run:
sudo chmod -R 2750 /var/www
NOTE: any directories that should be written to, like an uploads folder, will later have to be chmod’ed correctly, like:
sudo chmod -R 2770 /var/www/YOURAPPNAME.com/shared/public/images/uploads
Ruby Basic Install
To get the latest version of Ruby and all its essentials in preparation for Rails, run:
sudo aptitude install ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 irb1.8 libreadline-ruby1.8 libruby1.8 libopenssl-ruby sqlite3 libsqlite3-ruby1.8
Then create the symlinks where most ruby applications look for ruby components:
sudo ln -s /usr/bin/ruby1.8 /usr/bin/ruby sudo ln -s /usr/bin/ri1.8 /usr/bin/ri sudo ln -s /usr/bin/rdoc1.8 /usr/bin/rdoc sudo ln -s /usr/bin/irb1.8 /usr/bin/irb
You’ll now need RubyGems to complete your basic Ruby install. We’ll build the most recent version of RubyGems from its source. Don’t worry, it’s easy!
mkdir ~/sources cd ~/sources
Get the latest version from the RubyGems homepage: rubygems on rubyforge
It could be 1.3.1 or newer. Download it into your new sources directory, and extract it when done.
wget http://rubyforge.org/frs/download.php/XXXXX/rubygems-X.X.X.tgz tar xzvf rubygems-X.X.X.tgz cd rubygems-X.X.X
Let’s then run the install script (setup.rb), make the correct linkage, and make sure we have it installed afterwards with gem -v. gem -v should output “X.X.X” where that’s the same version you downloaded.
sudo ruby setup.rb sudo ln -s /usr/bin/gem1.8 /usr/bin/gem gem -v
Finally, we’ll make sure we have the latest and greatest base gem releases:
sudo gem update --system sudo gem update
Rails Install
Now you’re ready for the easy part: installing Rails. Simply install the rails gem, and double-check that you’ve gotten the right gems afterwards by running gem list.
sudo gem install rails sudo gem list
You should have the most recent version of: actionmailer, actionpack, activerecord, activeresource, activesupport, rails, and rake.
MySQL
We’ll now have to install MySQL and configure it’s root user:
sudo aptitude install mysql-server mysql-client libmysqlclient15-dev libmysql-ruby1.8 -y
Once installing, you’ll be prompted the screen to choose a root MySQL Password: mysql_install.jpg
Choose something you won’t forget, and write it down!
We’ll make sure that the MySQL tables for your application are created later, using this root password.
Git Installation
Git is the new sliced bread. To use it, you’ll want to make sure you have it installed on your server – and you’ll want the most recent version, too, since it’s being updated regularly. We’ll download it and build it in your ~/sources directory.
On a linux machine, we’ll first have to install git’s dependencies:
sudo aptitude install libssl libssl-dev sudo aptitude install build-dep git-core sudo aptitude install tk
Then we can download and build the most recent version. Go to git.or.cz and get the link to the most recent link for the source’s .tar.gz. Then configure, make the installer, and finally install it.
mkdir ~/sources --- if you haven't already cd ~/sources wget http://kernel.org/pub/software/scm/git/git-X.X.X.X.tar.gz tar xzvf git-X.X.X.X.tar.gz cd git-X.X.X.X ./configure make sudo make install
Postfix
If you want to send mail from the local server, you should also install the default version of postfix. If you’re going to use SMTP through GMail, however, you don’t have to do this.
sudo aptitude install postfix
Phusion Passenger Setup
With all basic web application components installed and ready to go, we’re ready for the Phusion Passenger setup.
First we’ll install the gem, and then the apache module:
sudo gem install passenger sudo passenger-install-apache2-module
Press ‘enter’ to pass the first screen (after you read it!). The next screen will inform you that you’re missing a dependency, which is easy to install – and it even tells you how. Here it is for convenience. After it’s installed, we’ll run the apache module install again.
sudo aptitude install apache2-prefork-dev sudo passenger-install-apache2-module
The instructions at the end tell us to update our config file with something like the following: LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-X.X.X/ext/apache2/mod_passenger.so
RailsSpawnServer /usr/lib/ruby/gems/1.8/gems/passenger-X.X.X/bin/passenger-spawn-server
RailsRuby /usr/bin/ruby1.8. Copy and paste the code it actually provides you into your apache config file. Then restart apache.
sudo nano /etc/apache2/apache2.conf ...[paste in line provided and save]... sudo /etc/init.d/apache2 restart
There’s just one more thing to do. We also want to instruct Passenger to be run with the same user as apache (www-data).
PassengerDefaultUser www-data
To do this, add the preceding line at the end of apache2.conf (the file you just edited), and restart apache to read the changes. Such as:
sudo nano /etc/apache2/apache2.conf ...[paste in line above and save]... sudo /etc/init.d/apache2 restart
We’ll cover the rest of the passenger setup for each app, in the “Web App Deployment Section”.
Web App Deployment
Whew! Our server is now primed to have our application installed on it. Now we have to prime our web application to be installed on the server! We’re going to make sure your web application code is setup in a Git Repository, with the right files in it, and the wrong ones out of it, and then setup capistrano files to deploy it with.
Turning your Web App into a Git Repository
Once you’ve installed git on your local machine, creating a git repository is easy. Simply run:
LOCAL$ git init
In the base directory of your rails app (the directory just above public). Most importantly, you want to keep the files you don’t want sitting in the repository, by setting up your .gitignore file at the base of your git repository. Here are the contents of my usual .gitignore files:
log/*.log tmp/**/* log/*.pid log/call_* db/schema.rb db/*.bkp .DS_Store *.swp *~ index/**/* config/database.yml config/s3.yml config/mail_servers.yml config/backup_fu.yml config/billing_on_rails.yml
MOST IMPORTANTLY, you want to make sure any files with passwords stay out of the repository. For many, this will just be database.yml. For apps with other plugins, this may also include s3.yml, mail_servers.yml, and billing_on_rails.yml, etc. These files will be linked back into your application on the server (or each of your servers), installed by hand, using a shared directory that Capistrano knows how to manage. We’ll cover that later.
We’ll now add all your files (sans the ones in .gitignore!) to the git repository, and then push your files to a remote git server. It’s good to have a seperate server just for your repository, so it can be updated, managed, and backed up correctly on its own. This is your intellectual property! If you’re using github, you’ll create a new repository on github, and then run:
LOCAL$ git add . LOCAL$ git commit -a -m "first commit, or a comment on changes" LOCAL$ git remote add origin git@github.com:githubusername/githubrepositoryname.git LOCAL$ git push origin master
That’s it!
Setting up Capistrano to deploy to your server from your git repository
First, you’ll need to download capistrano on your local box, with all of its dependencies:
LOCAL$ sudo gem install capistrano
Now you’re ready to create the capistrano setup files in your rails app! Simply:
LOCAL$ cd ~/myrailsapp LOCAL$ capify .
Doing so will give you two new files: ./Capfile and ./config/deploy.rb. We can ignore the ./Capfile and focus and ./config/deploy.rb To edit it, run your favorite text editor:
LOCAL$ nano config/deploy.rb
To make your life easier, you can replace the contents of your deploy.rb file with the one that follows. There are a few important points to make about it, however.
- Be sure to changeset :port, 30000 to the correct port for SSH
- You may not have as many YML files (with passwords) to symlink. You can get rid of the references to them
- You need to replace your :repository with your actual git repo URL
- touch #{current_path}/tmp/restart.txt will restart your passenger instance(s) automatically
set :application, "YOURAPPNAME.com"
set :user, "app"
set :use_sudo, false
set :repository, "git@github.com:githubusername/githubrepositoryname.git"
set :deploy_to, "/var/www/#{application}"
set :scm, :git
set :git_enable_submodules, 1 # Make sure git submodules are populated
set :port, 30000 # The port you've setup in the SSH setup section
set :location, "123.45.67.890"
role :app, location
role :web, location
role :db, location, :primary => true
namespace :deploy do
desc "Restart Application"
task :restart, :roles => :app do
run "touch #{current_path}/tmp/restart.txt"
end
desc "Make symlink for database.yml"
task :symlink_dbyaml do
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
end
desc "Create empty database.yml in shared path"
task :create_dbyaml do
run "mkdir -p #{shared_path}/config"
put '', "#{shared_path}/config/database.yml"
end
desc "Make symlink for s3.yml"
task :symlink_s3yaml do
run "ln -nfs #{shared_path}/config/s3.yml #{release_path}/config/s3.yml"
end
desc "Create empty s3.yml in shared path"
task :create_s3yaml do
run "mkdir -p #{shared_path}/config"
put '', "#{shared_path}/config/s3.yml"
end
desc "Make symlink for billing_on_rails.yml"
task :symlink_billingyaml do
run "ln -nfs #{shared_path}/config/billing_on_rails.yml #{release_path}/config/billing_on_rails.yml"
end
desc "Create empty billing_on_rails.yml in shared path"
task :create_billingyaml do
run "mkdir -p #{shared_path}/config"
put '', "#{shared_path}/config/billing_on_rails.yml"
end
end
after 'deploy:setup', 'deploy:create_dbyaml'
after 'deploy:update_code', 'deploy:symlink_dbyaml'
after 'deploy:setup', 'deploy:create_s3yaml'
after 'deploy:update_code', 'deploy:symlink_s3yaml'
after 'deploy:setup', 'deploy:create_billingyaml'
after 'deploy:update_code', 'deploy:symlink_billingyaml'
after "deploy", "deploy:cleanup"
It’s typically best to commit this code to your repository first, but you don’t have to, since Capistrano runs from your local machine to the server.
You also need to setup your local capistrano settings appropriately for GitHub use. Edit your ~/.caprc file, and place in the following options. For example, run:
LOCAL$ nano ~/.caprc
And paste in the following, and save it.
ssh_options[:forward_agent] = true ssh_options[:compression] = false default_run_options[:pty] = true
The first capistrano command to run (always from your local machine) cap deploy:setup. This logs into the server, and creates the appropriate directory structure at your deployment directory – in this case, "/var/www/#{application}".
LOCAL$ cap deploy:setup
Now, if you login to the server, you’ll see /var/www/shared and /var/www/releases. For each deployment, capistrano will put each release into its own directory within the releases directory.
Setup Shared YML Files
With the config/deploy.rb file provided above, cap deploy:setup also created empty versions of the YML files with your passwords. You’ll need to go into each of them, and provide the correct information. For example, you’ll want to populate your /var/www/YOURAPPNAME.com/shared/config/database.yml file with your database, username, password, and socket.
nano /var/www/YOURAPPNAME.com/shared/config/database.yml
This is a standard rails file, so we’ll you assume you know its format. However, it is important to know that Ubuntu’s MySQL .sock file lives in /var/run/mysqld/mysqld.sock. So your /var/www/YOURAPPNAME.com/shared/config/database.yml might look like this:
production: adapter: mysql database: myrailsapp_production username: myrailsapp_dbuser password: myrailsapp_dbpw socket: /var/run/mysqld/mysqld.sock
You should also edit any other empty yml files in /var/www/YOURAPPNAME.com/shared/config/, and fill in their contents.
Setup Your App’s Database
Now that you know what your database name will be on the server, along with it’s username and password for this server, you can go through and actually create the database. Run:
mysql -u root -p [you'll be prompted for your password] mysql> create database myrailsapp_production; mysql> GRANT ALL PRIVILEGES ON myrailsapp_production.* TO 'myrailsapp_dbuser'@'localhost' IDENTIFIED BY 'myrailsapp_dbpw' WITH GRANT OPTION;
Deploy The First Time!
Now that the server is ready to recieve your application, and capistrano is setup locally to deploy your application, you can deploy your app for the first time. To do so, run the following command on your local devleopment server.
LOCAL$ cap deploy:cold
A lot will scroll by, and with luck, everything will go smoothly. Your development box will login to your server via SSH, pull from your GitHub repository into the /var/www/YOURAPPNAME.com/releases/NNNNNN directory, and setup a symlink from /var/www/YOURAPPNAME.com/current to the most recent release.
Usually, capistrano runs the database migrations for you, when you run deploy:cold. If it doesn’t, or there is an error, you can run the migrations yourself on the server.
cd /var/www/YOURAPPNAME.com/current export RAILS_ENV=production rake db:migrate
For any updates from now on, you’ll be able to simply run the following command to deploy updates.
LOCAL$ cap deploy
Setup Passenger vhost
Before you can pull up your newly deployed Rails application in a browser, you’ll have to configure Phusion Passenger to your new application, which is residing in /var/www/YOURAPPNAME.com/current. Passenger uses apache vhosts to describe each application. So we’ll have to make a vhost file, and let apache know about this vhost file.
sudo nano /etc/apache2/sites-available/YOURAPPNAME
Simply copy in the following content into the new vhost file – it’s that easy! The DocumentRoot should point to the public directory of your rails app – the rails app which capistrano symlinked to with /var/www/YOURAPPNAME.com/current
NameVirtualHost *:80
<VirtualHost *:80>
ServerName domain1.com
ServerAlias www.domain1.com
DocumentRoot /var/www/YOURAPPNAME.com/current/public
</VirtualHost>
NOTE: if you don’t have a domain name pointing to your server, simply put your IP address for ServerName, e.g. ServerName 123.45.67.890.
We’ll now run the following commands to tell apache about this new vhost. We want to make sure that rewrite mode is enabled, too, because Rails relies on it. And finally, you’ll reload apache to get it to enable the new vhost.
sudo a2ensite YOURAPPNAME sudo a2enmod rewrite sudo /etc/init.d/apache2 reload
NOTE: If you run into problems, you can always check yoru apache log files (where Passenger writes to) at /var/log/apache2/.
Double check that everything is working now, by going to your domain name or IP address in your browser, e.g. http://123.45.67.890/
Remove default vhost
You might also want to disable the default site, which can ruin your new settings. Run:
sudo a2dissite default sudo /etc/init.d/apache2 reload
Sub-URI / Sub Directory Deployment
If you want your rails app to run on a specific path of your webserver, like mydomain.com/myrailsapp, follow the instructions at:
modrails.com/documentation/Users guide.html
SSL Certificates
Chances are that you will want to enable SSL. You’ll be provided three files from the authority you registered your SSL certificate with, and copy them to the correct path for your OS. The default installation paths on Ubuntu are:
sudo cp your_server_name.crt /etc/ssl/certs sudo cp gd_bundle.crt /etc/ssl/certs --- SSL Chain Certificate, if any sudo cp your_server_name.key /etc/ssl/private
So now we have the certificate we need to enable Apache mod_ssl, and then we’ll be directed to restart apache:
sudo a2enmod ssl sudo /etc/init.d/apache2 restart
You’ll notice you’ll be prompted with a passphrase. You’ll might want to remove it for automated restart reasons. To do so, visit
http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html
sudo openssl rsa -in /etc/ssl/private/thrivesmarthq.key -out /etc/ssl/private/thrivesmarthq.key
As an alternative approach you can use the ``SSLPassPhraseDialog exec:/path/to/program’’ facility. Bear in mind that this is neither more nor less secure, of course.
Finally, update your apache site file to enable ssl:
sudo nano /etc/apache2/sites-available/YOURAPPNAME
You’ll add in the NameVirtualHost *:443 command, and the entire <VirtualHost *:443> tag.
NameVirtualHost *:80
NameVirtualHost *:443
<VirtualHost *:80>
ServerName domain1.com
ServerAlias www.domain1.com
DocumentRoot /var/www/domain1.com/current/public
</VirtualHost>
<VirtualHost *:443>
ServerName www.domain1.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/www.domain1.com.crt
SSLCertificateKeyFile /etc/ssl/private/domain1.key
SSLCertificateChainFile /etc/ssl/certs/gd_bundle.crt
DocumentRoot /var/www/domain1.com/current/public
</VirtualHost>
Once you’re done, reload or restart apache:
sudo /etc/init.d/apache2 reload
Extras
ThriveSmart also makes use of the ‘dig’ command, which requires installing the dnsutils package:
$ sudo aptitude install dnsutils
Final Notes
If you have anything useful to add to this how-to, please feel free to say so in a comment below. We’ll try to put it in the right place, if it fits into the scope of this document!