Recommended ways of deploying Flask apps in the cloud

It’s been a while since my last post, what’s up everyone?

Today’s topic is being discussed quite a lot lately. Unlike PHP, there are multiple ways of running a Flask web application. Starting from something simple, like python app.py, to using apache2+mod_wsgi or even a more complex setup like nginx+uwsgi. Literally, there are tons of ways and setups that somebody can choose. But there’s gotta be a recommended way of running a Flask app in the cloud, for example in an EC2 VM, right?

We’ll get to it! First we need to draw a separating line between development and production as they defer a lot.

In a development-only environment, anything as simple as python app.py with app.run() inside the script would just work smoothly and in most cases there won’t occur any issues. But that’s in development environment, where you generally don’t give a damn about performance.

The time comes that your development-only application makes it to production. That’s when the fun begins. At this point, I may note that what I’ll describe from this point and above are just my personal preference and the setup of my choice after some years of experience running many kinds of Python apps in production.

The Gunicorn + Nginx approach

So I assume you’ve set up your Ubuntu server and your Python code already lives somewhere inside the VM, in it’s own virtualenv. Install Gunicorn along with the rest of your project’s requirements: pip install gunicorn and set an application-entry point, a file called wsgi.py.

wsgi.py

from myflaskapp import app

if __name__ == "__main__":
    app.run()

Now you can run in your terminal gunicorn --bind 0.0.0.0:8000 wsgi to check if gunicorn binds correctly your Flask app. If everything works okay until now, move on.

It’s time to configure our Nginx for reverse proxying.

Create a file myflaskapp inside sites-available: sudo nano /etc/nginx/sites-available/myflaskapp. In there we’ll open up a server block and tell Nginx to listen on the default port 80 for incoming requests, but only requests intended for our server’s domain name (or IP address).

Additionally, we need a location block that matches every request. There are some proxying parameters that need to be set as well, so we’ll remember to include them, too. All traffic requests will be handled from now on by the socket we define using the proxy_pass directive

server {
    listen 80;
    server_name server_IP_or_Domain_Name;

    location / {
        include proxy_params;
        proxy_pass http://unix:/path/to/myflaskapp/myflaskapp.sock;
    }
}

Restart Nginx sudo service nginx restart and the app should be up and running already on your IP or Domain specified in the config above. This should be enough in most cases. In fact, that’s the most preferred setup for Flask apps.

Things to consider before deploying

Well, to begin with, don’t expose Gunicorn or your WSGI server directly to internet traffic - it’s not a great idea to do. Reverse proxy requests to Gunicorn instead, as servers like Nginx or Apache are designed to handle much more traffic.

You may find yourself at a point where horizontal scalability is your day’s task. I get it, go on and fire up more Gunicorn workers. Your Flask app is a long-running Python process, and you should have an efficient way to manage such processes. A good suggestion would be using supervisor in that case, you can find more info regarding how to do that in a previous post of mine.

Hope this post helped a bit, happy to add more details as I’m free to any kind of feedback, just message me at kostas [at] intelligems [dot] eu.