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
- Apache is running (we’ll assume it’s version 2.4)
- WSGI is enabled for Apache
- 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
.