Deployment of PHP or Python Applications using Mercurial and Fabfile

Deployments are usually very painful. We generally write scripts to make it automated as much as possible. I wanted my method to be as easy as running single line on command prompt from anywhere in the world. I didn't want to worry about anything. At last with some experimentation I have found my way. This blog talks about the deployment of php/python applications using mercurial as code repo and fabfile. Well you actually can use any scripting format instead of fabfile. But fabile makes it easy to log into a remote machine and perform tasks. Also the scripting language of fabfile is python. This gives a lot of flexibility to customize and I dont have to learn anything new.

This process is inspired by Heroku git deployment feature. This tutorial works with hg, git and mostly with any other DVCS with minor alteration. It has two major steps
STEP 1: On your *nix Server

  1. Install mercurial on your server - it should be easy
  2. Setup SSH access to mercurial repository
    Your server should be able to login to code repository and pull the latest code. Its easier to use SSH than passwords.
    On your server machine:

    1. Open terminal
    2. Enter ssh-keygen
    3. Give a name or you can use the default name id_rsa
    4. When it asks "Enter passphrase (empty for no passphrase):" press enter. No Password
    5. Once the key generation is complete. You can verify the same using ls -a ~/.ssh
    6. Add this new identity to SSH agent ssh-add ~/.ssh/id_rsa
    7. Now we need to add this public key to bitbucket or any other provider cat ~/.ssh/id_rsa.pub. Copy the output
    8. For bitbucket, go to account -> SSH keys, add the above output to your ssh keys

    Now your server is set to access your repositories with out the need of password.

  3. Now we need to setup the Hg repository inside web accessible directory. For example, your web accessible folder could be
    /home/user_home/public_html
    or /var/www/html
    or in case of phython it can be anywhere /home/user_home/my_project_code
  4. To deploy php application tweet4blood, clone the repo inside the directory using ssh url

    cd /home/user_home/public_html
    hg clone ssh://hg@bitbucket.org/thejeshgn/tweet4blood tweet4blood.com
  5. Make sure to make the .hg folder (actual repository) inaccessible to the webserver either by .htaccess rule or changing the permissions etc

STEP 2: On your desktop

  1. Install fabfile or fabric. On ubuntu search for fabric in Synaptic Package Manager
  2. Create your fabfile, you can find the latest version of below example fabfile in my snippets project
  3. To call any method in fabfile

    $ cd /home/thej/my_deployment_scripts/tweet4blood/
    $ fab hello

  4. Fabric can also chain the calls

    $ cd /home/thej/my_deployment_scripts/tweet4blood/
    $ fab test hello

    Here it calls the test method first which sets the env variables and then calls hello
  5. None of the env variables are necessary but providing env.user, env.hosts, env.password will avoid typing them everytime
  6. BTW env.user, env.hosts, env.password are that of SERVER machine
  7. To deploy the latest version to test

    $ cd /home/thej/my_deployment_scripts/tweet4blood/
    $ fab test deploy:tip

    In this case,

    • test method sets the env variables corresponding to TEST env
    • test method also sets application env specific consumer_key which later we will use to setup config.php, similarly you can use define databas_name, database_user_name etc
    • then as per chain deploy method is called with input variable version whose value now is "tip"
    • inside deploy the first call is cd (change directory) on remote server
    • at this point fab logs into the remote server using env.user, env.hosts, env.password
    • then control goes to the repo directory
    • runs hg pull which gets everything from bitbucket
    • runs hg update -C tip which is clean update to "tip" version
    • then CDs into auth folder
    • uses the Linux sid command to replace the env specific values in config.php
  8. To deploy any other version to test. I usually tag the versions, so I will pass the tag name

    $ cd /home/thej/my_deployment_scripts/tweet4blood/
    $ fab test deploy:v.0.1.0

  9. In case you want to shutdown the apache before the deployment and restart later, you can chain them too
    $ cd /home/thej/my_deployment_scripts/tweet4blood/
    $ fab test apache_stop deploy:tip apache_start

STEP 3: Go deploy
Below I have embedded a rough version of fabfile.py for quick refrence. But as I told you can find the latest version of the same in my snippets.

Questions and suggestions are welcome.

################################################################
# It uses fabfile to build and deploy
# Ref: https://thejeshgn.com/2012/02/01/deployment-php-python-app-using-mercurial-and-fabfile
# usage:
#        cd to the folder
#        $ fab hello    
#
#To deploy tagged version to test:
#        cd to the folder which has fab file
#        $fab test deploy:REPO_TAG_NAME
#
#To deploy latest version to test:
#        cd to the folder which has fab file
#        $fab test deploy:tip
#
#To deploy to prod:
#        cd to the folder which has fab file
#        $fab prod deploy:REPO_TAG_NAME
#
#

################################################################
from __future__ import with_statement
from fabric.api import *
from fabric.contrib.console import confirm

def hello():
    print("Welcome to web deployment.")
    
def test():
    env.user = 'user_name'
    env.hosts = ['100.100.100.100']
    env.password ='password'
    env.consumer_key ="test_xGxRxzXrxoxSxrxYxuxdxnxLxBxzQU00Q"

def prod():
    env.user = 'user_name'
    env.hosts = ['200.200.200.200']
    env.password ='password'
    env.consumer_key ="prod_xGxRxzXrxoxSxrxYxuxdxnxLxBxzQU00Q"

def update(version):  
    with cd('/home/user_home/public_html/tweet4blood.com'):  
        run('hg pull')      
        run('hg update -C '+version)      
    with cd('/home/user_home/public_html/tweet4blood.com/html/auth/'):
            consumer_key_defnition = "define('CONSUMER_KEY', '"+ +"');"                         
            run("sed -i 's/define('CONSUMER_KEY', 'xGxRxzXrxoxSxrxYxuxdxnxLxBxzQU00Q');/"+consumer_key_defnition+"/' config.php")

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

def apache_stop():  
    sudo('/etc/init.d/apache2 stop') 

def apache_start():  
    sudo('/etc/init.d/apache2 start')
 
def apache_status():
    sudo('ps aux | egrep "(PID|apache2)"')

def deploy(version):
    update(version)   

3 Responses

  1. Arun says:

    Actually, I use a rakefile and git to achieve pretty much the same for my blog. Deploying is a one-liner: rake deploy:prod on the source tree :)

  2. Thejesh GN says:

    For some reason its always been
    git and ruby
    mercurial and python
    together. I wonder.

  3. S Anand says:

    I tend to use Makefiles for deployment. It’s usually (a) rsync to server (b) ssh some-command-at-server. Probably because my compilation process already uses make.