Deploying wsgi applications behind Nginx

Published on: 16 Feb 2009 by Anders Conbere

There's a fine line in the world of web frameworks, if you dictate too many choices in for your users, they loose the ability to carry in knowledge they might already have, they feel constrained, and you dictate what can be accomplished. If you let things fly free, the number of choices crushes the developers ability to be productive. Werkzeug clearly lands more on the former, but helps bridge that gap with a great set of documentation, that lays out a clear path forward. The choices are there for you to make, but if you want to get going, the dev team there has provided you with a great set of defaults, and clear documentation on how to utilize them.

One place that the documentation for Werkzeug has lacked is on the topic of deployment. This was amended with the latest release but still didn't cover my use case, or at least didn't go into the amount of detail I would expect from deployment documentation. So I thought I would cover how I use Werkzeug in my deployed applications.

As a brief overview, I like to deploy my applications with easy_install behind Nginx, using Paster Serve as my application host.

Creating a setup.py install script

from setuptools import setup, find_packages

setup(name='werkzeug_app',
    version='0.1',
    description='werkzeug_app',
    author='Anders Conbere',
    author_email='aconbere@gmail.com',
    url='http://anders.conbere.org/',
    packages=find_packages(),
    package_data = {'werkzeug_app': ['templates/*']},
    classifiers=['Development Status :: 1 - Alpha',
                 'Environment :: Web Environment',
                 'Intended Audience :: Developers',
                 'Operating System :: OS Independent',
                 'Programming Language :: Python',
                 'Topic :: Utilities'],

    install_requires = [
        "Werkzeug > 0.4.0",
        "Jinja2 >= 2.0",
        "SQLAlchemy >= 0.5.0",
        "Paste >= 1.7.0",
        "PasteDeploy >= 1.3.0",
        "PasteScript >= 1.7.0",
        "simplejson >= 2.0.0",
        "WTForms >= 0.3.1",
        ],

    entry_points={
        'paste.app_factory': [
            'main=werkzeug_app.application:app_factory',
            ],
        },
      )

This does a couple of things, that are nice for deploying. It defines my dependencies, and automatically installs them for me when I run the script. It set's up an entry point for Paster to grab onto later, and attaches some simple metadata about the application to it.

To install your app, now all you have to do is run

$> sudo python setup.py install

Building a Paster config for running your app<

In order to have Paster run out servers we need to give it some basic configuration parameters.

[server:main]
    use = egg:Paste#http
    host = 0.0.0.0
    port = 8090
    use_threadpool = True
    threadpool_workers = 10

[app:main]
    use = egg:werkzeug_app
    db_uri = sqlite:////var/db/werkzeug_app/werkzeug_app.db

It should be relatively clear from this what's going on. The first set of config params sets up how our server works, defines the IP it binds to and a Port. The second set tells Paster where to find our code. Since we installed our app using easy_install as an egg, we need to tell Paster to look for that. On top of that we can pass extra data to our application at this point, so I choose to send in my database path as part of the config.

If you wanted to run many of these servers to load balance you would simply need to copy the two sets of configs for the "main" app, and give it a new name like "main2", and give that app a new ip or port to bind to. They could then be started separately.

These apps can then be run by issuing the paster serve command

> paster serve developemnt.ini --daemon
> paster serve -n main2 developement.ini --daemon

Setting up Nginx to point back to your paster servers

I use the following simple Nginx config (this is probably not the best nginx config ever I just don't bother much with it).

worker_processes  2;

events {
    worker_connections  1024;
}

http {
    client_body_timeout   5;
    client_header_timeout 5;
    keepalive_timeout     5 5;
    send_timeout          5;
    tcp_nodelay on;
    tcp_nopush  on;

    upstream werkzeug_apps {
        server localhost:8090;
        server localhost:8091;
    }

    server {
        listen       80;
        server_name  localhost;
        location / {
            proxy_pass http://werkzeug_apps;
            proxy_redirect default;
        }
    }
}

once again, you can run this by starting nginx, which can be invoked by running

> nginx -c nginx.conf

Breathe deeply and think about how awesome you are

Seriously, you rule! And now you not only rule, but have nginx hosting your werkzeug app as well.<