Rails Application Deployment with Git
Now that I've gotten this (and several client websites) on solid Rails foundations, I thought I might share how I'm handling deployments for the websites. I'm using Git as the version control system, so naturally, I'm using Gitosis for my main repository. I have a very simple script that is installed as the hook in my Gitosis repository that deploys my changes to the live system.
But it might be worth a few minutes to discuss the "why". Why use Git hook scripts when you can just FTP or scp data right into the web root. In a word: permissions. In dealing with our private cloud at work, and here on jbrisbin.com, UNIX file permissions are always popping up and biting me in the rear. In a traditional environment, where I change files locally then FTP them to the web root, I have to make the web root directory writable by the user I'm FTPing as, of course. This means opening up my web root directory with more lenient permissions than I would prefer. Or I could set a password on the www-data user my webserver runs as and FTP the files up using that user. I've tried both ways before and I don't like either.
The ideal solution is to have a process deploy my files for me and have that process run under the www-data user so I don't have to worry about file permissions. Thanks to my Git repository hook script and a trivial setting in the /etc/sudoers file, I can let Git handle my deployment whenever I push my local changes up to the server.
Git
Since this system depends on Git, you'll want to make sure you have a proper Gitosis repository set up that you can push your Rails application to. Setting that up is not as difficult as you might think. The original, seminal guide to Gitosis is still quite informative and I suggest you read it: Hosting Git repositories, The Easy (and Secure) Way. But thanks to the Ubuntu package maintainers, installing Gitosis on Ubuntu 10.04 is as simple as doing:
# sudo apt-get install gitosis
This installs the basic tools required to host a Git repository. You'll still need to follow the setup steps in the guide I just linked to. It entails copying your SSH public key to the server and initializing Gitosis with that key. Then you can create a repository in your local Rails app and push it to your remote Gitosis repository.
The Hook Script
Now that you've got a Git repository on the machine to which you're deploying, you're ready to write the hook script. For several years prior to switching to Git, I hosted our live, internal web portal on an SVN working copy that was updated by an SVN hook script. You could do the same thing here and simply host your Rails app from a working copy of the files. This would be handy to pick up file deletes, which Git would do for you. I chose not to go this route for no real reason. Instead, I'm using the git archive command to export a clean artifact. I pipe this tar file into a special script that deploys the files and runs a Rails migration to pick up any incoming database changes:
#!/bin/sh
git archive --format=tar HEAD | (/path/to/rails-deploy $DOMAIN)
The helper script (rails-deploy) does a couple things:
- Deploys the files to web root with a
tar -x - Runs
rake db:migrateto pick up any incoming changes
#!/bin/sh
sudo -u www-data tar -C /path/to/webroot/$1 -xf -
cd /path/to/webroot/$1
sudo -u www-data /usr/local/bin/rake db:migrate RAILS_ENV=production
The key thing to notice here is the sudo. I'm running this tar command as my webserver user. In order to actually let this work when you're pushing files into your remote repository, you have to edit the /etc/sudoers file to allow the user gitosis to issue specific commands as the user www-data. The relevant parts of /etc/sudoers look like this:
Runas_Alias WWW = www-data
[...]
gitosis ALL = (WWW) NOPASSWD: /bin/tar, /usr/local/bin/rake
Without explaining the relative complexities of sudo, suffice it to say this lets the user gitosis issue a sudo command to execute the /bin/tar and /usr/local/bin/rake commands as the user www-data. You can read up on editing the sudoers file in the Sudoers Manual.
nginx/Passenger
You only need to have nginx set up to have /path/to/webroot/$DOMAIN/public set as your root. When you commit your changes to your local copy of the Rails app and issue a git push, you should be able to go to your browser, hit refresh, and (after nginx has restarted your Rails app) see your new changes published live.
No scp, FTP, or kludgey user permission setups needed!