Automatic App Deployment with “git push”

When deploying pet projects on remote servers, I dislike the extra step of logging into the remote machine to execute a git pull (and perhaps a server reload) every time I push new code. Sure there’s Jenkins and all sorts of bazooka-like solutions, but this is a knife fight. Poking around an old PHP repo, I found a nifty line of code that will update your app via a URL endpoint.

<?php `git pull`;

Drop that into secret-update-url.php and now all you have to do is visit http://mysite.com/secret-update-url to get the latest version of your code on the site. Going a step further, you can set up a webhook to have Github hit the URL for you.

Once I had dreamed the impossible dream in PHP, I wanted to have the same power with Python. Here’s a Flask route that accomplishes the same task:


@app.route('/secret-update-url', methods=['POST'])
def github_hook():
    try:
        data = json.loads(request.form['payload'])
        site_url = 'https://github.com/USERNAME/REPO_NAME' # replace me
        site_branch = 'master'
        data_url = data.get('repository', {}).get('url')
        data_branch = data.get('ref')
        if data_url == site_url and site_branch in data_branch:
            log.info("Code change detected. Restarting now...")
            os.system('git pull')
            # if you're using Flask,
            with open('/path/to/this-application.pid', 'r') as f:
                pid = int(f.read())
                os.kill(pid, signal.SIGHUP)
    except ValueError:
        log.error("Unable to parse Github payload")
    return 'ok'

This code is a little more robust than the PHP example, checking to see if we’re on the right branch before issuing the git pull. If you’re using gunicorn as an HTTP server you’ll find that simply pulling new code is not enough — you also have to restart the server to see the changes. Assuming you’re starting gunicorn with the -p application.pid flag, the code above will read the PID and kill the process, causing the server to restart.

Finally, here’s how to accomplish the task in Node using the handy connect-githubhook package.


var express = require('express'),
    cgh = require('connect-githubhook'),
    sites = {'/secret-update-url': {url: 'https://github.com/user/repo',
                                    branch: 'master'},
    app = express.createServer();

var handler = function(repo, payload) {
    console.log('Code change detected. Restarting now...');
    setTimeout(function() {
        process.exit(1);
    }, 1000);
};

app.use(express.bodyParser()); //required
app.use(cgh(sites, handler));

app.listen(8000);

Voila! Easy code pulls and server restarts for PHP, Python, and Node. Note that there isn’t any authentication on the /secret-update-url so code up a little auth before thinking about using this for production knife fights.