Configure the Production Server

Learn how to configure the production server on Heroku.

Locking Pipfile

The first step to preparing the production server is making sure it has everything it needs. We’ve already discussed the database dependency separately, but the server also requires a number of Python packages, such as SQLAlchemy, as well as the packages SQLAlchemy requires, and so on. Heroku makes life easier by grabbing the right versions of each dependency from the Pipfile.lock file in our repositories and then finding all the packages we’ll need. Generally, the lock file stays up-to-date since it re-locks every time we install or uninstall a package. We can double-check that the lock file is up-to-date by running the following:

$ pipenv lock
Locking [dev-packages] dependencies
✓ Success!
Locking [packages] dependencies
✓ Success!
Updated Pipfile.lock (7dc556)!

Python addon

The application uses Python and, in general, Heroku does a good job of identifying a Python environment by finding the Pipfile. However, we will also be using the nginx buildpack, and there have been some instances where this sometimes causes Heroku to miss the Python—in particular, if we’ve installed the nginx buildpack and not also the Python one before our first push, in which case it will fail to recognize the Python environment. . To take the randomness out of it, it’s suggested that we first push the repository that has the Pipfile and then set up the nginx second. If we don’t do that, we can explicitly add the Python buildpack. Here’s what it might look like if we first install nginx, then add Python separately.

$ heroku buildpacks:add heroku/python
Buildpack added. Next release on intense-bayou-52717 will use:
  1. heroku-community/nginx
  2. heroku/python
Run git push heroku master to create a new release using these buildpacks.

NGINX and gunicorn addon

If you haven’t heard of NGINX before, you may be wondering why we need it. The issue is that we want to serve the application in a way that is scalable and also efficiently supports static files. Recall the warning we got when we started a Flask application using flask run:

WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.

Web Server Gateway Interface

This warning appeared regardless of whether we run it in debug mode or not, or whether we set FLASK_ENV to a dev status or not. The problem is that the Flask server was never meant for production. By default, it’s a blocking server, meaning that it can only handle one user at a time. Also, getting a server to scale well is difficult, and Flask’s server doesn’t do it. This doesn’t mean we have to start over, though. Flask allows us to define applications that are compatible with better servers.

The key here is the idea of a production WSGI server. The acronym WSGI stands for Web Server Gateway Interface, which is a way of defining web applications so that deploying them can be done in a standard way. Flask makes a WSGI-friendly application, so if you have a WSGI server, you can serve the application. Many different WSGI servers exist and are being used in production on the internet, such as Green Unicorn, more commonly known as Gunicorn, Twisted Web, Tornado, uWSGI, mod_wsgi, and CherryPy.

All of these and more are competing to be your WSGI server. Opinions vary, but Gunicorn has emerged as a solid choice for use with Python applications. Switching to another option is unlikely to help us except in advanced and special cases. To install it, use pipenv:

$ pipenv install gunicorn

We can run the Gunicorn server on the computer by pointing it to our application. We do this by specifying the package and the Python variable for the application, separated by a colon. In the case of Lang-man, we can do this on our machine using:

$ export FLASK_ENV=dev_postgres
$ export FLASK_SECRET_KEY=somesecret123123123
$ pipenv run gunicorn server.app

Note: The Gunicorn server uses port 8000 by default, unlike the Flask server, which uses port 5000.

Reverse proxy

If we only had the Flask-based API to worry about, then we’d be set. This will be able to handle all requests to the API. However, we also have to worry about the client, which is just a few static files that also need to be served. One way to do this would be to add new routes within the Flask server and have it respond with those files. This is difficult to maintain, complicates the code unnecessarily, and is slow. The generally preferred approach is to use another server that gets the first chance to handle HTTP requests. If it doesn’t have the answer, it delegates to Gunicorn. Such a server is called a reverse proxy. Again, there are many options, but we’ll go with a common favorite, NGINX.

Get hands-on with 1200+ tech skills courses.