Problem:

You are a good little Python developer and you use virtualenv in your Python development.  If you aren’t using virtualenv you’re doing it wrong. You have one of the following problems/desires:

  • You want/need to use a Python virtual environment in your production deployment to encapsulate your configuration.
  • You have multiple Apache virtual hosts on the same machine and/or you are running several Python/Django applications with different or conflicting versions of Python packages.
  • Solution:

    I will demonstrate the solution by installing a Pinax application.  Pinax is a website framework built on top of Django.  Pinax has many dependencies (including a specific version of Django). We will use Pinax version 0.7.3 which requires Django 1.0. I found the the official Pinax deployment documentation very weak and it did not work for me out of the box.  Have no fear. The following steps should get you going.  You can also use this blog post as a general tutorial on production deployment of Django applications.  This solution was tested on Ubuntu 10.10 64-bit server edition.

    Step 1: Install system pre-requisites:

    $ sudo apt-get update
    $ sudo apt-get install git-core build-essential python-setuptools python2.6-dev

    Step 2: Install `pip`, `virtualenv`, and ‘virtualenvwrapper':

    $ sudo easy_install pip

    Step 3: Install ‘virtualenv’ and ‘virtualenvwrapper’ with pip:

     $ sudo pip install virtualenv virtualenvwrapper

    Step 4: Setup virtualenv wrapper

    This will let us use the command “workon” and say “workon ve” to switch to the “ve” virtual environment.  All we need to do is add a couple items in our .baschrc file. You can use any text editor to do this but I will give the commands using “nano”.  Nano is a terminal based text editor pre-installed with Ubuntu.  “vi” and “emacs” are also options.

    $ cd ~ 
    $ nano .bashrc

    Add the following lines to the bottom of the file and then save it by typing CTL-x and then hit ‘y’ to confirm the save.

    export WORKON_HOME=$HOME/.virtualenvs
    source /usr/local/bin/virtualenvwrapper.sh

    Now activate virtualenv wrapper by re-running the .bashrc

    $ source .bashrc

    Step 5: Download and install Pinax

    Before we download Pinax, here is a little background. The Pinax installer will use pip to fetch other prerequisites and create the virtual environment.  This is special to Pinax. Under a non-Pinax configuration, you would download your app, create a virgin virtalenv, activate it, and then run pip to fetch prerequisites and install them to the new virtualenv.  For clarity and completeness I’m going to illustrate the these basic steps here. I’ll use an imaginary project name “foobar” (You don’t need to do this for Pinax):

    $ wget http:/somewebsite/foobar.tar.gz
    $ tar zxvf foobar.tar.gz
    $ mkvirtualenv foobar-env --no-site-packages
    $ workon foobar-env
    (foobar-env)$ cd foobar
    (foobar-env)$ pip install -r requirements.txt

    This will install the prerequisites in the file “requirements.txt” to the virtual environment named “foobar-env”.

    Okay back to the task at hand.  Let’s install Pinax. Download and untar/unzip Pinax:

    $ wget http://downloads.pinaxproject.com/Pinax-0.7.3-bundle.tar.gz
    $ tar zxvf Pinax-0.7.3-bundle.tar.gz
    $ cd Pinax-0.7.3-bundle/

    Create the virtualenv and install the prerequisites.  I’m calling my virualenv “pinax-env”.   This step could take a while.

    $ python scripts/pinax-boot.py $WORKON_HOME/pinax-env

    Activate (i.e. switch to) the virtualenv.

    $ workon pinax-env

    Next let’s create a directory called projects to hold our actual project

    (pinax-env)$ cd ~
    (pinax-env)$ mkdir projects
    (pinax-env)$ cd projects

    Now lets clone a new Pinax project based on the Pinax intranet_project base. The first line will display a list of all project types.

    (pinax-env)$ pinax-admin clone_project -l
    (pinax-env)$ pinax-admin clone_project intranet_project myintranet

    Install Python Imaging Library (PIL) which seems to be missing from Pinax installer

    (pinax-env)$ pip install PIL

    Step 6 : Fire Up the Pinax Project

    (pinax-env)$ cd myintranet/
    
    (pinax-env)$ python manage.py syncdb
    
    (pinax-env)$ python manage.py runserver

    Point your browser at http://localhost:8000/ and you should see the Pinax default homepage.  If you are hosting remotely you may want to do the following:

    (pinax-env)$ python manage.py runserver 0.0.0.0:8000

    This will make Django serve over the Internet.  Note this is only development server.   We are just verifying things are working up to this point.  Now here is where it gets interesting.  This blog post is about deployment on Apache!  Now we want to make install Apache 2, mod_wsgi, and tell Apache to serve the Pinax/Django project located at “~/projects/myintranet” using our virtualenv called “pinax-env.  I’m using “ubuntu” as the default username.  So our home directory is “/home/ubuntu/”, our project directory is “/home/ubuntu/projects/myintranet” and the root of our virtalenvs is “/home/ubuntu/.virtualenvs/”.  Change this as needed to suit your system.

    Step 7: Install Apache2 and mod_wsgi.

    Hint don’t install mod_python too.  This can mess things up.

    $ sudo apt-get install apache2 libapache2-mod-wsgi

    Step 8: Configure Apache to serve our project

    We need to tell Apache to server our project.  We do this by pointing Apache to a special  python-based WSGI configuration file for our project. For Pinax, a sample file is located at “deploy/pinax.wsgi” . For Apache, the main file you need to edit is “/etc/apache2/sites-available/default”.  Add the following lines in blue  to “/etc/apache2/sites-available/default”:

     
    
    <VirtualHost *:80>
     
    ServerAdmin webmaster@localhost
     
    DocumentRoot /var/www
     
    Deny from all
     
    Allow from 127.0.0.0/255.0.0.0 ::1/128
     
    </Directory>
    <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /var/www <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /var/www/> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory> WSGIScriptAlias / /home/ubuntu/projects/myintranet/deploy/pinax.wsgi < Directory /home/ubuntu/projects/myintranet/deploy> Order deny,allow Allow from all </Directory> ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ <Directory "/usr/lib/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> ErrorLog ${APACHE_LOG_DIR}/error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog ${APACHE_LOG_DIR}/access.log combined Alias /doc/ "/usr/share/doc/" <Directory "/usr/share/doc/"> Options Indexes MultiViews FollowSymLinks AllowOverride None Order deny,allow Deny from all Allow from 127.0.0.0/255.0.0.0 ::1/128 </Directory>

    Now its time to complete our configuration by editing “home/ubuntu/projects/myintranet/deploy/pinax.wsgi”.  Pinax.wsgi file didnt work for me but the configuration I gleamed from mod_wsgi’s wiki on Virtual Environments will work.  Completely erase the default pinax.wsgi file and replace it with the following:

    ALLDIRS = ['/home/ubuntu/.virtualenvs/pinax-env/lib/python2.6/site-packages',
    '/home/ubuntu/projects',]
    
    import os
    import sys
    import site
    
    # redirect sys.stdout to sys.stderr for bad libraries like geopy that uses
    # print statements for optional import exceptions.
    sys.stdout = sys.stderr
    prev_sys_path = list(sys.path)
    
    for directory in ALLDIRS:
      site.addsitedir(directory)
    
    # Reorder sys.path so new directories at the front.
    new_sys_path = []
    for item in list(sys.path):
        if item not in prev_sys_path:
            new_sys_path.append(item)
            sys.path.remove(item)
    sys.path[:0] = new_sys_path 
    
    activate_this = '/home/ubuntu/.virtualenvs/pinax-env/bin/activate_this.py'
    execfile(activate_this, dict(__file__=activate_this))
    from os.path import abspath, dirname, join
    from site import addsitedir
    
    sys.path.insert(0, abspath(join(dirname(__file__), "../../")))
    
    from django.conf import settings
    os.environ["DJANGO_SETTINGS_MODULE"] = "myintranet.settings"
    
    sys.path.insert(0, join(settings.PINAX_ROOT, "apps"))
    sys.path.insert(0, join(settings.PROJECT_ROOT, "apps"))
    
    from django.core.handlers.wsgi import WSGIHandler
    application = WSGIHandler()

    The key things that may change if the project path and/or virtual environment change are highlighted in blue.  Note you want to remove the following lines if you are setting up a regular Django project (one that is not based on Pinax).

    sys.path.insert(0, join(settings.PINAX_ROOT, "apps"))
    sys.path.insert(0, join(settings.PROJECT_ROOT, "apps"))

    Step 9: Restart Apache and Cross your fingers

    $ sudo apache2ctl restart

    If the gods are smiling on you today you should be in business.  check by going to http://localhost/.  If not, you might want to check Apache’s error log.

    $ cat /var/log/apache2/error.log

    There are many other Django and Pinax specific setting you will likely want to change.  Please see the Django Project and the Pinax Project for more information. Good luck. :-)