Using Fabric for Web Development

If your development workflow involves programming on local environments, you may have found it cumbersome to interact with shared development servers. (I for one definitely have!) Here's a simple example: my favorite terminal app is open to a local directory, where I can use Drush, Compass, or other command-line tools to accomplish various task. I then push code to a shared development environment, where I'd like to immediately perform some of those same tasks. And that means I now have to open another tab of my app, find credentials for the remote server, login, and move to the correct directory, right? Not necessarily.

Fabric to the rescue

Several years ago, a few tools started to appear on the horizon that helped with running SSH commands on one or more remote servers. Capistrano and Fabric were the first two that really came across my radar. I ended up choosing Fabric for a couple reasons:

  1. I liked that it was Python, which is really just personal preference.
  2. Fabric seemed more like an SSH tool (which I liked) while Capistrano was a bit more of a distribution tool. Things have changed around a bit, but I still tend to go with Fabric.

So how does Fabric work? You create a "fabfile" which is a Python file or module and you create tasks. Tasks are generally a command or set of commands you want to run bundled into a function. These can be things you want to run locally or on the remote system (or a mix of both). You can create tasks to transfer files or run a quick database backup. You can then run a command like fab mytask and that will run your task or tasks. You can run a single command or multiple.
Here's how I end up configuring things on my own system.

Create a fabfile directory

Somewhere in your home directory, create a directory called "fabfile". Add a Python file called __init__.py in this folder. This is generally how you create a Python module. I find a module works better for Fabric as we can easily separate our tasks over several files. The file can be blank, but we're going to create a couple of files within our module. For each new file, we will add a new import statement to include that in our Fabric module.
It's helpful to create an alias for this fabfile directory. With Fabric, you can specify a fabfile that should load. Rather than type "fab --fabfile ~/fabfile" over and over again, we can create an alias: alias myfab="fab --fabfile ~/fabfile".
Next, I like to create two files: hosts.py and server.py. The hosts file is a bit special. The file contains tasks for connecting to a remote host. In Fabric, you can configure your hosts in many ways, but I find using tasks the most useful. Here's an example task:

@task
def example(environment="dev"):
if environment == 'dev':
env.hosts.extend(['rob@dev.example.com'])
elif environment == 'prod':
env.hosts.extend(['rob@example.com'])

So if I want to connect to this host, I run: fab hosts.example (connects to the development environment) or fab hosts.example:prod (connects to the production environment). You can specify more environments, or even multiple servers if you have multiple web servers for example. Note that these connections work best when you are using key-based authentication but Fabric will prompt for any input that it needs along the way.

Tasks for good

Tasks are limited only by what is available to you via SSH and Python. Along with the hosts file above, we created a server.py file for server related commands. I like to store commands here that are useful for dealing with most servers. A couple task examples from that file include:

@task
def restart_apache():
"""Restart apache"""
sudo('/sbin/service httpd restart')

For Drupal specific functions (namely Drush commands), I create a drupal file and include that in my module. The Drupal file contains many different things including functionality to clear the cache, install a new module, and other tasks you might want to undertake without having to connect to each server and running Drush from there.

@task(alias='en')
def drush_dis(module):
"""Disable a module"""
with cd(env.hosts_path):
run('drush pm-disable %s' % module)
run('drush clear-cache all')
@task(alias='en')
def drush_en(module):
"""Enable a module"""
with cd(env.hosts_path):
run('drush pm-enable %s' % module)
run('drush clear-cache all')

But what about Drush?

Now some may point out that Drush and many Drush extensions can accomplish some of this. And that's very true. There are a couple factors that personally draw me to use Fabric though: not every project may have Drush or Drupal available (you may even be working on a non-Drupal site of all things!). Now you have a single interface for running commands across all of your projects. Also, many of the same tasks you create for one project can be used with systems like Jenkins or cron or just other scripts that may need to use an SSH connection. And of course, if you have multiple servers in your production or developments environments, you can run the same task on all of them using Fabric, instead of one at a time. The possibilities are not limited of course to what I've demonstrated here and you can use them with systems outside of a normal PHP/MySQL stack.
Check out Fabric and see how it can improve your development processes!

Code Drupal Planet

Read This Next