Deploying a Flask app part I: the gunicorn WSGI server

Last year I wrote a post about deploying Flask apps with Apache/mod_wsgi when your app’s dependencies are installed in a conda environment. The year before, in the dark times, I wrote a post about the black magic invocations required to get multiple apps running stably using mod_wsgi. I’ve since moved away from mod_wsgi entirely and switched to running Flask apps from containers using the gunicorn WSGI server behind an Apache reverse proxy, which has made life immeasurably easier. In this post we’ll cover running a Flask app on localhost using gunicorn; in Part II we’ll run our app as a service using Singularity and deploy it to production using Apache as a HTTP proxy server.

The simplest Flask app

Suppose we have a minimal Flask app, myapp.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    print("Hello, Flask!")

We can run this using Flask’s built-in HTTP server as follows:

flask run myapp:app

This is perfectly acceptable for local development and testing, but is not suitable for production as Flask’s built-in server is neither performative nor secure.

Flask is a WSGI (Web Server Gateway Interface) application. WSGI is a standardised interface for web servers to communicate with Python applications, allowing any WSGI application to be run by a WSGI server. A WSGI servers include an HTTP server, allowing them to receive HTTP requests from a client (i.e. your browser) and communicate with a WSGI application (i.e. our Flask app). Many such servers are available, but here we’ll use the gunicorn WSGI server as it’s quite efficient and works well out of the box.

Installing gunicorn

We can install gunicorn using pip:

pip install gunicorn

If your app uses a virtualenv or conda environment, you can simply install gunicorn in that environment. If your app will need to pause for extended periods when handling requests (e.g. querying a backend database or waiting for a subprocess) you will want to install a suitable asynchronous worker class to allow your app to continue handling requests while backend code is running. For example, we can install the gevent worker class:

pip install greenlet
pip install gevent

We can run our Flask app using gunicorn as follows:

gunicorn myapp:app

This will run our app on localhost, just like Flask’s built-in server. We can specify the port, workers, log location, and many other options. For example, to use four gevent workers for an app running on localhost port 5000:

gunicorn --bind 127.0.0.1:5000 --worker-class gevent

There are a lot of options, so you’ll probably want to use a configuration file. See the documentation for details.

Wrapping up

And that’s it! Your Flask app is now running using a production-ready WSGI server. Of course, it’s running on localhost. To deploy our app, we’ll want to set up an HTTP proxy server in front to handle things like HTTPS and buffering. In Part II we’ll set up a basic Apache reverse proxy and make sure our Flask app is ready to run behind a reverse proxy.

Author