Deploying a Flask application on Apache

Deploying a Flask application on Apache comes with a challenge or two, but the following recipe should get you going.

Requirements

Before deploying a Flask application on the server you need to ensure that

  1. Apache is running (we’ll assume it’s version 2.4)
  2. WSGI is enabled for Apache
  3. Python is installed in a virtual environment

Configuring Apache

Assuming that your Flask app runs on a domain of its own, you need to define a virtual host with WSGI support. This can be done in a separate configuration file, as long as that file is included by the main configuration file. Here is an example of such a file for production use.

<VirtualHost *:80>
DocumentRoot "/path/to/your/app/dir"
ServerName my.app
SetEnv DBHOST localhost
Alias /fonts /path/to/your/app/dir/static/fonts
WSGIScriptAlias / /path/to/your/app/dir/wsgi.py
<Directory /Users/christian/Sites/WSGI>
Require all granted
WSGIPassAuthorization On
WSGIScriptReloading On
</Directory>
</VirtualHost>

For development use you’d replace the line

Alias /fonts /path/to/your/app/dir/static/fonts

with

Alias /bower_components /path/to/your/app/dir/bower_components

Note the WSGIPassAuthorization directive. If this isn’t set to On, the Authorization response header isn’t passed to the Flask application, which is really bad news if you are using token based authentication.

Remember that the server name (my.app in the example above) must exist. For testing purposes you may just add it to your /etc/hosts file.

The WSGI file

The Flask app is run via a WSGI file (which we’ve called wsgi.py in the Apache config above). The first thing this file needs to do is to start the virtual environment for Python.

activate_this = '/path/to/your/app/dir/venv/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

Yes, there really exists a file with the name activate_this.py!

Of course it would be nice if there was no need for hard-coding the path for activate_this.py. If the virtual environment has the same name and relative path to the wsgi.py file wherever you deploy the application, you can achieve this by modifying the code a bit:

activate_this = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             os.pardir,
                             'venv',
                             'bin',
                             'activate_this.py')
execfile(activate_this, dict(__file__=activate_this))

Next it must be ensured that Python knows about the application code.

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)

Note that there is no need to hardcode any path, as the __file__ variable conveniently gives the path of the WSGI file,

If life were simple we could now invoke the application by just importing the application:

from my_app import app as application

But life isn’t simple, at least if you have taken Miguel Grinberg’s advice to heart to define confidential settings such as passwords in environment variables. In the Apache configuration we included an environment variable DBHOST, but WSGI doesn’t make the system’s environment variables available via os.environ.

One suggestion would be to access the environment variables via Flask’s request.environ. But this approach is fraught with the problem that you quite possible need to access the environment variables before the request context has been initialised.

So the better solution is to make use of the fact that WSGI passes the system’s environment variables as a parameter when calling the application function and to explicitly define this function.

def application(environ, start_response):
    # explicitly set environment variables from the WSGI-supplied ones
    ENVIRONMENT_VARIABLES = ['DBHOST']
    for key in ENVIRONMENT_VARIABLES:
	    os.environ[key] = environ.get(key)

    from my_app import app
	
    return app(environ, start_response)

The complete wsgi.py looks as follows.

import os
import sys

activate_this = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             os.pardir,
                             'venv',
                             'bin',
                             'activate_this.py')
execfile(activate_this, dict(__file__=activate_this))

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)


def application(environ, start_response):
    # explicitly set environment variables from the WSGI-supplied ones
    ENVIRONMENT_VARIABLES = [
        'FAULT_TRACKING_LOG_FILE',
        'FAULT_TRACKING_DATABASE_URI',
        'FAULT_TRACKING_DEV_DATABASE_URI',
        'FAULT_TRACKING_TEST_DATABASE_URI',
        'FAULT_TRACKING_ADMIN_USERNAME',
        'FAULT_TRACKING_LDAP_URI'
    ]
    for key in ENVIRONMENT_VARIABLES:
        os.environ[key] = environ.get(key)

    from app import create_app

    app = create_app('production')
    return app(environ, start_response)

Go!

After restarting Apache your application should now be available at http://my.app.

References

  1. http://flask.pocoo.org/docs/0.10/deploying/mod_wsgi/
  2. http://ericplumb.com/blog/passing-apache-environment-variables-to-django-via-mod_wsgi.html