2 Mar 2011 mcr   » (Journeyer)

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"

# This is where Rails migrations will run
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/

Syndicated 2011-03-02 12:06:00 (Updated 2011-03-17 18:14:24) from Michael's musings

Latest blog entries     Older blog entries

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!