Explore Flask

13.2. The stack

This section will cover some of the software that we'll need to install on our server to serve our Flask application to the world. The basic stack is a front server that reverse proxies requests to an application runner that is running our Flask app. We'll usually have a database too, so we'll talk a little about those options as well.

13.2.1. Application runner

The server that we use to run Flask locally when we're developing our application isn't good at handling real requests. When we're actually serving our application to the public, we want to run it with an application runner like Gunicorn. Gunicorn handles requests and takes care of complicated things like threading.

To use Gunicorn, we install the gunicorn package in our virtual environment with Pip. Running our app is a simple command away.

# app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
        return "Hello World!"

A fine app indeed. Now, to serve it up with Gunicorn, we simply run the gunicorn command.

(ourapp)$ gunicorn rocket:app
2014-03-19 16:28:54 [62924] [INFO] Starting gunicorn 18.0
2014-03-19 16:28:54 [62924] [INFO] Listening at: http://127.0.0.1:8000 (62924)
2014-03-19 16:28:54 [62924] [INFO] Using worker: sync
2014-03-19 16:28:54 [62927] [INFO] Booting worker with pid: 62927

At this point, we should see "Hello World!" when we navigate our browser to http://127.0.0.1:8000.

To run this server in the background (i.e. daemonize it), we can pass the -D option to Gunicorn. That way it'll run even after we close our current terminal session.

If we daemonize Gunicorn, we might have a hard time finding the process to close later when we want to stop the server. We can tell Gunicorn to stick the process ID in a file so that we can stop or restart it later without searching through lists of running processess. We use the -p <file> option to do that.

(ourapp)$ gunicorn rocket:app -p rocket.pid -D
(ourapp)$ cat rocket.pid
63101

To restart and kill the server, we can run kill -HUP and kill respectively.

(ourapp)$ kill -HUP `cat rocket.pid`
(ourapp)$ kill `cat rocket.pid`

By default Gunicorn runs on port 8000. We can change the port by adding the -b bind option.

(ourapp)$ gunicorn rocket:app -p rocket.pid -b 127.0.0.1:7999 -D

13.2.1.1. Making Gunicorn public

Caution Gunicorn is meant to sit behind a reverse proxy. If you tell it to listen to requests coming in from the public, it makes an easy target for denial of service attacks. It's just not meant to handle those kinds of requests. Only allow outside connections for debugging purposes and make sure to switch it back to only allowing internal connections when you're done.

If we run Gunicorn like we have in the listings, we won't be able to access it from our local system. That's because Gunicorn binds to 127.0.0.1 by default. This means that it will only listen to connections coming from the server itself. This is the behavior that we want when we have a reverse proxy server that is sitting between the public and our Gunicorn server. If, however, we need to make requests from outside of the server for debugging purposes, we can tell Gunicorn to bind to 0.0.0.0. This tells it to listen for all requests.

(ourapp)$ gunicorn rocket:app -p rocket.pid -b 0.0.0.0:8000 -D

Note

  • Read more about running and deploying Gunicorn in the documentation.
  • Fabric is a tool that lets you run all of these deployment and management commands from the comfort of your local machine without SSHing into every server.

13.2.2. Nginx Reverse Proxy

A reverse proxy handles public HTTP requests, sends them back to Gunicorn and gives the response back to the requesting client. Nginx can be used very effectively as a reverse proxy and Gunicorn "strongly advises" that we use it.

To configure Nginx as a reverse proxy to a Gunicorn server running on 127.0.0.1:8000, we can create a file for our app: /etc/nginx/sites-available/expl-oreflask.com.

# /etc/nginx/sites-available/exploreflask.com

# Redirect www.exploreflask.com to exploreflask.com
server {
        server_name www.exploreflask.com;
        rewrite ^ http://exploreflask.com/ permanent;
}

# Handle requests to exploreflask.com on port 80
server {
        listen 80;
        server_name exploreflask.com;

                # Handle all locations
        location / {
                        # Pass the request to Gunicorn
                proxy_pass http://127.0.0.1:8000;

                # Set some HTTP headers so that our app knows where the 
                # request really came from
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

Now we'll create a symlink to this file at /etc/nginx/sites-enabled and restart Nginx.

$ sudo ln -s \
/etc/nginx/sites-available/exploreflask.com \
/etc/nginx/sites-enabled/exploreflask.com

We should now be able to make our requests to Nginx and receive the response from our app.

Note The Nginx configuration section in the Gunicorn docs will give you more information about setting Nginx up for this purpose.

13.2.2.1. ProxyFix

We may run into some issues with Flask not properly handling the proxied requests. It has to do with those headers we set in the Nginx configuration. We can use the Werkzeug ProxyFix to ... fix the proxy.

# app.py

from flask import Flask

# Import the fixer
from werkzeug.contrib.fixers import ProxyFix

app = Flask(__name__)

# Use the fixer
app.wsgi_app = ProxyFix(app.wsgi_app)

@app.route('/')
def index():
        return "Hello World!"

Note