Deploying Django applications with Capistrano

Yesterday, I cooked up a deploy.rb so that Capistrano can deploy a Django application. While there is a Python app called http://docs.fabfile.org/0.9.0/ from what I could tell, it was very general to running commands on multiple servers, and not really specific to checking out a web framework and deploying it to one or more servers.

First, my deploy.rb, and then my notes about how I used it. I have changed only one or two things from my real code. My application is called “clientportal” and the host running it is called “clientportal.isp.example.net”. On the server, it runs as a user called “clientportal”.

This code does not yet invoke the Django database migrations, which it ought to, and I’ll do another blog post once I figure out that part.

set :application, "clientportal" set :me, "#{ENV['LOGNAME']}" set :repository, "git+ssh://#{me}@code.credil.org/git/path/to/repo/clientportal" set :scm, :git set :user, :clientportal set :ssh_options, { :forward_agent => true } set :use_sudo, false set :git_enable_submodules, true set :deploy_to, "/home/#{user}/#{application}" role :web, "clientportal.isp.example.net" # Your HTTP server, Apache/etc role :app, "clientportal.isp.example.net" role :db, "clientdb.isp.example.net", :primary => true namespace :deploy do task :start do ; end task :stop do ; end # this overrides a rails specific thing. task :finalize_update do ; end task :migrate do ; end task :restart, :roles => :app, :except => { :no_release => true } do # something to restart django. run "sudo /usr/sbin/apache2ctl graceful" end task :update_database_yml, :roles => [:app,:web] do db_config = "/home/#{user}/settings.py" run "cp #{db_config} #{release_path}/settings.py" run "ln -f -s #{release_path} /home/clientportal/clientportal/clientportal" puts "Ran update database settings" end end after "deploy:update_code", "deploy:update_database_yml"

Some details. First, I put my settings.py file into my /home/clientportal directory. I do not check this file into my repo, because it always specific to the installation (it’s different on your laptop than on the devel server or the production server). Also see my:

Like http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/ I had to adjust my django.wsgi file as well. I wound up with:

import site site.addsitedir('/usr/local/pythonenv/CLIENTPORTAL/lib/python2.5/site-packages') import os, sys sys.path.append('/home/clientportal/clientportal') sys.path.append('/home/clientportal/clientportal/current') os.environ['DJANGO_SETTINGS_MODULE'] = 'clientportal.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler()

The important changes were to the path that was added. It used to add $HOME/clientportal and $HOME to the path, but now it is one directory deeper, and you will notice above in the update_database_yml task that it creates a symlink in $HOME/clientportal with the name “clientportal” that is essentially the same as “current”.

This is necessary because the settings are loaded as “clientportal.settings”, and python basically turns the . into a / when looking for the file. I could have just changed the name of the settings file, but we had other modules that were loaded using the clientportal. namespace.

Note that the server already had it’s apache configured to do what was needed. I would normally package these config files up into a .deb file, but I haven’t done that yet for this project, it being my first django project.

I am not sure if I actually have to restart apache. I added that for good luck, and and I added:

clientportal ALL=NOPASSWD: /usr/sbin/apache2ctl graceful

to sudoers.

My apache config looks like:

<VirtualHost *:443> ServerAdmin webmaster@localhost ServerName clientportal.isp.example.net ServerAlias portal1.isp.example.net ServerAlias portal.example.net DocumentRoot /home/clientportal/clientportal/current <Directory "/home/clientportal/clientportal/current"> Options Indexes FollowSymLinks Options -MultiViews AllowOverride None Order allow,deny allow from all </Directory> ErrorLog /var/log/apache2/error.log Alias /media/ /home/clientportal/clientportal/current/media/ WSGIScriptAlias / /home/clientportal/clientportal/current/wsgi/django.wsgi <Directory /home/clientportal/clientportal/current/apache/> Order allow,deny Allow from all </Directory> ...

Some other links I found, but I didn’t use much: http://groups.google.com/group/django-developers/browse_thread/thread/f34e59275e04f9c5?pli=1 http://gnuvince.wordpress.com/2008/01/10/deploying-django/