Published on April 13, 2011.
TweetIf you ever used Heroku (or any similar service), you may be jealous of their straightforward way to deploy new versions of applications. At least, I am.
That’s why I came up with a couple of hacks to mimic their git-based deployment workflow, but for my own Django applications, served by Gunicorn.
The basic idea is: your application code is contained in a Git repository, and your production deployments are as simple as a git push.
Note: because we will type commands on both the client and server side, server-side commands will start by a > and client-side commands by a $. All commands are executed by a regular user.
First of all, let’s prepare the ground by installing all the software we need on our production server (for the record, I used a Lucid Vagrant box):
> sudo apt-get install git-core
> sudo apt-get install python-virtualenv
> sudo apt-get install nginx
Now that we have the software, let’s create the base structure for our production website. In my configuration, I’m in my home, which is /home/vagrant/.
> mkdir www
> cd www
> virtualenv --no-site-package .
> mkdir var
The var directory will contain the pidfile and the gunicorn socket file. You could also use it to store a sqlite database if you are running a test server for example.
Switch to your desktop shell and create a basic Django application (I call it codebase) along with a git repository:
$ django-admin.py startproject codebase
$ cd codebase
$ git init .
We also add the Pip requirements file (because you use Pip to manage your module dependencies right?): $ echo "django\ngunicorn" > requirements.pip.
Now we have to create a simple bash script which will be executed right after you push your new code to the server. I put it in the root of the project tree because I think it’s a good thing to versionize it with git. So open your favorite text editor and write the deploy.sh script:
#!/bin/sh
# Remember that this script will be executed by the unix user who push to the
# git repository ; and the script will be executed in ~/www/codebase/.
# Update requirements
../bin/pip install -r requirements.pip
# Reload the Gunicorn instance
kill -HUP `cat ../var/pid`
# Ideas of other things to do:
# Apply South migrations?
# Clear cache?
Now we add a first commit to our local repository:
$ git add .
$ git commit -a -m "bare django project"
First, clone your local repository into the codebase direcory:
> git clone /vagrant/codebase /home/vagrant/www/codebase
Note: /vagrant/ is a mount point to my desktop machine.
In order to make git do the automatic things, we have to tweak its server-side configuration. So add the following to the > codebase/.git/config file:
[receive]
denyCurrentBranch = "ignore"
It prevents Git from moaning about the fact that you push to a non-bare repository.
We also have to create a git hook so as it runs the deploy.sh script we wrote earlier. Write the following in the > codebase/.git/hooks/post-receive:
#!/bin/sh
# update the source tree
cd ..
env -i git reset --hard
# execute the deploy.sh script
sh deploy.sh
Don’t forget to > chmod +x codebase/.git/hooks/post-receive.
In a normal production use, you should run Gunicorn using supervisord or anything like it. But for this example, I will run directly Gunicorn from the command line:
> bin/gunicorn_django --pid var/pid --socket var/socket codebase/settings.py
Now you can easily push your changes to the production site. Just edit some files, commit, and git push ssh://yourserver/www/codebase/.git master and you’re done. Requirements will be installed and Gunicorn will be gracefully reloaded.
The configuration is not done yet: we don’t take care of settings! We obviously need to swap development settings with production settings. You can use my Rails-like configuration for Django to do this (it works flawlessly).