Setting Up a Production Django Environment on Ubuntu Karmic
Posted on November 8th, 2009 in Django, Server Setup, Web Development | Comments
I love tutorials. It’s time I put a few of my own online.
This one will focus on setting up a complete Django production environment on Ubuntu Karmic. It will use nginx, Apache and mod_wsgi, MySQL, and virtualenv.
Install Everything
First, lets install everything we need:
apt-get install build-essential apache2 php5 php5-cgi libapache2-mod-php5 libapache2-mod-wsgi mysql-server mysql-client libmysqlclient15-dev php5-mysql php5-mcrypt nginx python-setuptools
This should bring up a window to set your MySQL root password, but if not:
mysqladmin -u root password [NEW PASSWORD]
Replacing [NEW PASSWORD] with whatever you want your root password to be.
So that was pretty easy. Let’s install virtualenv and virtualenvwrapper.
easy_install virtualenv
easy_install virtaulenvwrapper
Set up VirtualEnvWrapper
Let’s create two directories in our /var/www folder; one of them will be for PHP vhosts, and one for Python vhosts.
mkdir -p /var/www/php /var/www/python
We need to add some lines to our .bashrc file to get virtualenvwrapper to work correctly.
export WORKON_HOME=/var/www/python
source /usr/local/bin/virtualenvwrapper_bashrc
Configure PHP CGI
In order to serve php with nginx (as we are with phpMyAdmin), we need to setup php-cgi.
Put the following into /etc/init.d/php-fastcgi:
#! /bin/sh
### BEGIN INIT INFO
# Provides: php-fastcgi
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start and stop php-cgi in external FASTCGI mode
# Description: Start and stop php-cgi in external FASTCGI mode
### END INIT INFO
# Author: Kurt Zankl <[EMAIL PROTECTED]>
# Do NOT "set -e"
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="php-cgi in external FASTCGI mode"
NAME=php-fastcgi
DAEMON=/usr/bin/php-cgi
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
PHP_CONFIG_FILE=/etc/php5/cgi/php.ini
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
# If the daemon is not enabled, give the user a warning and then exit,
# unless we are stopping the daemon
if [ "$START" != "yes" -a "$1" != "stop" ]; then
log_warning_msg "To enable $NAME, edit /etc/default/$NAME and set START=yes"
exit 0
fi
# Process configuration
export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS
DAEMON_ARGS="-q -b $FCGI_HOST:$FCGI_PORT -c $PHP_CONFIG_FILE"
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
--background --make-pidfile --chuid $EXEC_AS_USER --startas $DAEMON -- \
$DAEMON_ARGS \
|| return 2
}
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE > /dev/null # --name $DAEMON
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
exit 3
;;
esac
And the following into /etc/default/php-fastcgi:
START=yes
# Which user runs PHP? (default: www-data)
EXEC_AS_USER=www-data
# Host and TCP port for FASTCGI-Listener (default: localhost:9000)
FCGI_HOST=localhost
FCGI_PORT=9000
# Environment variables, which are processed by PHP
PHP_FCGI_CHILDREN=4
PHP_FCGI_MAX_REQUESTS=1000
Make the init script executable:
chmod +x /etc/init.d/php-fastcgi
Make it permanent:
update-rc.d php-fastcgi defaults
Then turn it on:
/etc/init.d/php-fastcgi start
Install phpMyAdmin
Let’s get the current version of phpMyAdmin:
cd /tmp
wget http://downloads.sourceforge.net/project/phpmyadmin/phpMyAdmin/3.2.3/phpMyAdmin-3.2.3-english.tar.gz?use_mirror=voxel
tar xzvf phpMyAdmin-3.2.3-english.tar.gz -C /var/www/php/
mv /var/www/php/phpMyAdmin-3.2.3-english /var/www/php/phpmyadmin
Create a copy of config.sample.inc.php:
cd /var/www/php/phpmyadmin
cp config.sample.inc.php config.inc.php
And change the blowfish secret on line 18.
We’re going to use Nginx and FastCGI to serve phpmyadmin – since we want to dedicate Apache to our Python stuff. Now, create a new nginx site to serve phpMyAdmin (nano /etc/nginx/sites-available/phpmyadmin), replacing the IP and server_name below with your external IP and domain name:
server {
listen 80;
server_name phpmyadmin.sk3vy.com;
access_log /var/log/nginx/php/phpmyadmin-access.log;
error_log /var/log/nginx/php/phpmyadmin-error.log;
location / {
root /var/www/php/phpmyadmin;
index index.php;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/php/phpmyadmin$fastcgi_script_name;
include /etc/nginx/fastcgi.conf;
}
}
And in /etc/nginx/fastcgi.conf:
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
Enable the site by creating a symlink:
ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled/phpmyadmin
And create the log directory:
mkdir -p /var/log/nginx/php
Don’t restart Nginx yet, lets finish up the rest of this stuff first.
Install MySQLdb
I like to install MySQLdb python extension first so that it’s included in the virtualenv’s that we make.
apt-get install python-mysqldb
Make sure it worked by going into a python shell (type python at the command prompt) and then type import MySQLdb. If you don’t get any errors, the installation has worked successfully.
Setup a Virtual Environment and Django Project
Make a virtual environment and change into that directory:
mkvirtualenv testenviron
cdvirtualenv
Install django and pip (for better package management):
easy_install django
easy_install pip
Create a new django project:
django-admin.py startproject testproject
cd testproject
Setup Mod_WSGI in your Django project
To get mod_wsgi working, we have to create a wsgi file in our project directory:
mkdir apache
nano apache/django.wsgi
In this file, put the following:
import os
import sys
# put the Django project on sys.path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))
os.environ["DJANGO_SETTINGS_MODULE"] = "testproject.settings"
from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()
Setup Apache to Serve the Project
Disable the default site:
a2dissite default
Change your /etc/apache2/ports.conf file to look like this:
NameVirtualHost *:8080
Listen 8080
<IfModule mod_ssl.c>
# SSL name based virtual hosts are not yet supported, therefore no
# NameVirtualHost statement here
Listen 443
</IfModule>
This sets Apache to listen on port 8080, instead of port 80. Nginx will listen on port 80 and proxy requests to the Apache process.
Then, we need to create an Apache vhost to serve the project:
nano /etc/apache2/sites-available/testproject
Put in the following:
<VirtualHost *:8080>
ServerName testproject.sk3vy.com
WSGIDaemonProcess testproject-production processes=2 threads=15 display-name=%{GROUP} python-path=/var/www/python/testenviron/lib/python2.6/site-packages
WSGIProcessGroup testproject-production
WSGIScriptAlias / /var/www/python/testenviron/testproject/apache/django.wsgi
<Directory /var/www/python/testenviron/testproject>
Order deny,allow
Allow from all
</Directory>
ErrorLog /var/log/apache2/python/testproject-error.log
LogLevel warn
CustomLog /var/log/apache2/python/testproject-access.log combined
</VirtualHost>
Make some directories for the logs:
mkdir /var/log/apache2/python
Enable the site:
a2ensite testproject
Setup Nginx to Proxy Through to the Project and Serve the Media Files
Remove the default site:
sudo rm /etc/nginx/sites-enabled/default
Create a new nginx site for your testproject (nano /etc/nginx/sites-available/testproject), replacing the IP and server_name below with your external IP and domain name:
server {
listen 80;
server_name testproject.sk3vy.com;
access_log /var/log/nginx/python/testproject-access.log;
error_log /var/log/nginx/python/testproject-error.log;
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/proxy.conf;
}
location /media/ {
root /var/www/python/testenviron/testproject/;
}
}
Create a symbolic link to enable the project:
ln -s /etc/nginx/sites-available/testproject /etc/nginx/sites-enabled/testproject
Create a /etc/nginx/proxy.conf file:
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
Create the log directories:
mkdir -p /var/log/nginx/python
Restart Apache and Nginx:
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/nginx restart
This finishes the tutorial. Everything should be set up properly, and to create a new virtual host, just follow the steps starting from “Setup a Virtual Environment and Django Project”.
