Django on a Hetzner-managed server

How to install a Django web application on a Hetzner-managed server

[2010-11-15]

I am a big fan of Hetzner. I have been a customer of theirs for a long time. Their service is absolutely fantastic. And I have no complaints regarding their servers either.

Although I am root on a little virtual server on the interwebs, I prefer a production site to sit on a managed server. Hetzner is very good at what they do, and I am humble enough to admit that they are better at doing it than I am. And if that were not enough reason, I have plenty of work to keep me busy, so I'd rather not add "maintaining solid web servers" to my To Do list. I am happy to let them do that for me.

Hetzner's managed servers offer PHP, Perl and CGI—actually FastCGI. They run PHP via FastCGI too. But they don't run Python in a way that makes serving Python-based web apps simple. Their managed servers run Debian Lenny, so they come with Python 2.5. But they don't come with any Python database libraries. (They do come with cjson, though, which might be useful.)

Recently I discovered how to install Django applications using Python's virtualenv and Django's FastCGI installation method. And this works very nicely on Hetzner boxes.

The only tricky part is the MySQLdb installation, because it needs dev packages that are not available on the server. So I installed a Debian Lenny i386 virtual box on my own machine, and once the Python MySQLdb library was built, I uploaded it to the Hetzner box. To save you the hassle of having to build it yourself, I'll add a link to it below.

So here follows a step-by-step walk-through of how to get a Django app running happily (and quite fast) on a Hetzner-managed server.

Install virtualenv

SSH into the server, and install virtualenv ...

myuser@hetzn1:~$ mkdir src
myuser@hetzn1:~$ cd src
myuser@hetzn1:~/src$ wget http://bitbucket.org/ianb/virtualenv/get/tip.gz
--2010-11-15 16:46:14-- http://bitbucket.org/ianb/virtualenv/get/tip.gz
Resolving bitbucket.org... 207.223.240.182, 207.223.240.180, 207.223.240.181
Connecting to bitbucket.org|207.223.240.182|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1477934 (1.4M) [application/x-tar]
Saving to: `tip.gz'

100%[======================================>] 1,477,934 30.3K/s in 81s

2010-11-15 16:47:40 (17.9 KB/s) - `tip.gz' saved [1477934/1477934]

myuser@hetzn1:~/src$ tar -xzf tip.gz
myuser@hetzn1:~/src$ python virtualenv/virtualenv.py --distribute ~/python_virtualenv
New python executable in /usr/home/myuser/python_virtualenv/bin/python
Installing distribute...........................................................
................................................................................
.........................................done.
myuser@hetzn1:~/src$ cd ~/python_virtualenv/

Upload the lib directory I've prepared, which includes MySQLdb and Django1.2 ...

Here are the pre-built libraries.

As an appendix I'll show you how I installed them (not much to it really, but unless you have a Debian Lenny virtual machine lying around, it can be time-consuming).

myuser@hetzn1:~/python_virtualenv$ wget http://norman.hooper.name:8080/norman_hooper_name/var/python_virtualenv_lib.tar.gz
--2010-11-15 22:33:01-- http://norman.hooper.name:8080/norman_hooper_name/var/python_virtualenv_lib.tar.gz
Resolving norman.hooper.name... 196.1.61.226
Connecting to norman.hooper.name|196.1.61.226|:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6263965 (6.0M) [application/octet-stream]
Saving to: `python_virtualenv_lib.tar.gz'

100%[======================================>] 6,263,965 1.06M/s in 5.9s

2010-11-15 22:33:08 (1.01 MB/s) - `python_virtualenv_lib.tar.gz' saved [6263965/6263965]

myuser@hetzn1:~/python_virtualenv$ rm -rf lib
myuser@hetzn1:~/python_virtualenv$ tar -xzf python_virtualenv_lib.tar.gz
myuser@hetzn1:~/python_virtualenv$ rm python_virtualenv_lib.tar.gz

That's it!

Starting your virtual environment

$ source ~/python_virtualenv/bin/activate 

This makes it easier to use the manage.py script in your Django project (alternatively, you can add the virtualenv directories to the PYTHONPATH environment variable).

Deploying

Let's check out what we need to get this web app installed. I've put the FastCGI script in a sub-directory of ~/public_html because we don't want media to be served via Django. You could go with convention, and use ~/public_html/cgi-bin/ but I just used ~/public_html/s/ to keep it short ...

myuser@hetzn1:~/python_virtualenv$ cd /usr/home/myuser/public_html/s
myuser@hetzn1:~/public_html/s$ cat .htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ myproj.fcgi/$1 [QSA,L]

Unlike the example given in the Django documentation, in the case of Hetzner you don't need to associate the .fcgi extension with FastCGI. Simply sending the request to the fcgi script should work.

myuser@hetzn1:~/public_html/s$ cat myproj.fcgi 
#!/usr/bin/python

import sys, os

sys.path.insert(0, "/usr/home/myuser/python_virtualenv/lib/python2.5")
sys.path.insert(1, "/usr/home/myuser/python_virtualenv/lib/python2.5/site-packages")
sys.path.insert(2, "/usr/home/myuser/src/myproj/src")

os.environ['DJANGO_SETTINGS_MODULE'] = "myproj.settings"

from django.core.servers.fastcgi import runfastcgi
runfastcgi(method="threaded", daemonize="false")

Change the shebang in manage.py from ...

#!/usr/bin/python

... to ...
#!/usr/bin/env python

... so that once you start your virtual environment with ...
$ source ~/python_virtualenv/bin/activate 

... manage.py will use your Python, instead of the one in /usr/bin/

The FastCGI process is going to run as your user on the managed server. So you can create symlinks to files and directories under your home directory. This makes uploading updates simple.

myuser@hetzn1:~/public_html/s$ cd ..
myuser@hetzn1:~/public_html$ mkdir media
myuser@hetzn1:~/public_html$ cd media
myuser@hetzn1:~/public_html/media$ ln -s /usr/home/myuser/src/myproj/src/myproj/media/ myproj
myuser@hetzn1:~/public_html/media$ ln -s /usr/home/myuser/python_virtualenv/lib/python2.5/site-packages/django/contrib/admin/media admin

Now updates are as simple as ...

$ bzr upload 

... from your workstation (if you're using Bazaar) or an rsync.

Appendix: How to install Django and MySQLdb

If you'd rather build your own python_virtualenv/lib/ directory, here's how I did mine.

Install a minimal Debian Lenny i386 virtual machine. I used the business card ISO.

Install the following additional packages: python-dev libmysqlclient15-dev

Install virtualenv as described above.

Then use pip (included in virtualenv) to install Django, MySQLdb, flup (required for running Django via FastCGI) and Python Imaging Library (required for models.ImageField). You'll do this like so ...

$ cd ~/python_virtualenv
$ source bin/activate
$ pip install Django
$ pip install MySQL-python
$ pip install flup
$ pip install PIL
$ tar -czf python_virtualenv_lib.tar.gz lib

Then you can scp the tar.gz file up to your Hetzner account.

Help

If my instructions don't work for you, check out Django's documentation regarding FastCGI.

I also referred to Joe Maller's great blog post about running Django with CGI, using virtualenv.

You can find out more about virtualenv here.