Fabricate Your Way to Better Deployment

Most people have a favourite way of rolling out a new version of a website or similar project. Often this involves copying files to the server, making sure they're in the right place, setting permissions, possibly extracting a tarball of files and odd other server-side commands. Some of the sites I've inherited have had a pretty arcane list of magic incantations to get them to work. Deploying to multiple servers brings its own particular range of nightmare situations. For small deployments, where only one person needs to know how to update a site or the instructions can be followed easily by other developers, this isn't always a problem. There must be a better way, right? There is.

For one deployment where we had two webservers and a test server, I wrote a couple of shell scripts which would be run on each server in turn to pull down the latest stable build of the site from the Subversion, put everything in place, and then restart the server. I looked briefly at Capistrano at the time, but the version of RHEL we had on the servers wasn't up to the job.

When a couple of projects I worked on started to get a bit more complicated to deploy (multiple servers, big directories of mostly-unchanging assets and the like) I felt I needed something a bit more robust than just updating from the most recent stable version in the repository. So, after a bit of googling, I came across Fabric. This is a deployment tool written in Python, and designed to send files and commands to remote servers. So, how does it work?

Just a Fabrication

Somewhere, typically in the root of your project, you put the a file called "fabfile". This contains some details for logging into your server, and some functions that define commands. You pass a list of commands to Fabric, and it goes off and does them.

Here's an example of the fabfile for this site:

# fabfile
def production():
    config.fab_hosts = ['www.gjcp.net',]
    config.fab_user = "gordonjcp"
    config.app_path = "/var/www/gjcp.net/site/"

def restart():
    sudo("/etc/init.d/apache2 restart")

def deploy():
    run("cd $(app_path); git checkout master; git pull")
    invoke(restart)

def rollback():
    run("cd $(app_path); git checkout HEAD~1")
    invoke(restart)

For clarity, I've left out the testing command, but that basically just looks like production with a different path. The key functions here are run(), sudo(), and invoke(). It's worth pointing out that every time you run sudo() or run() (and there's also local() which works on the local system), it does so in a new shell. This means that you must pass the whole command as a one-liner, but that's no biggie. So what happens here? Well, when I'm happy with my latest change to the site, I commit it and push to the git repository, then type fab production deploy. Fabric will then run the production command, so it knows where to go, and then the deploy command which changes to the directory for the site, ensures that the git repository is pointing at the latest version, and updates. Then it uses invoke() to call a function to restart the server (I use mod_python which requires Apache to be restarted after changes). If something is wrong, I can say fab production rollback and it will check out the last version but one, which should be the previous stable version. Simple.

Taking it further

Because you can define a user in the fabfile to log into the server and combine this with ssh keys, you can make it easy to update the remote server without needing to follow complex instructions. There are other Fabric commands like get() and put() to receive and send files from your local machine to the server. For example, you could put together a command to pull down a copy of the current log files to examine locally, or (like one of the examples I saw) run all the unit tests on your app before deploying it.

Fabric has made deploying even simple sites a lot easier and less error-prone. Coupled with version control and unit tests, it's a powerful way to keep your website code up to date.