Deploying flask apps using gunicorn and nginx

Introduction

I couldn't find a good tutorial for setting up flask to work with gunicorn and nginx, so I decided to write some extremely simple steps to get your flask app to run on a (sub)domain. This tutorial won't go over advanced settings or running gunicorn as a daemon.

Gunicorn

Intro

Gunicorn is a HTTP server, inspired by Ruby's Unicorn that works with different python web frameworks, and most importantly, is the simplest web server to get started using.
You should be using virtualenv for your dependencies, but it's okay if you're not.

Installation

Let's first install gunicorn:

pip install gunicorn

(You may need to run the above command with sudo, if you don't use virtualenv.)

Running

Let's see if gunicorn works with our flask setup. cd into the directory containing your app, run:

gunicorn file:app -b 0.0.0.0:8000

This makes the server publicly available on all public IPs.

I have my files hosted on DigitalOcean (ref link), so I have a public IP for my server. Visiting http://my-public-ip:8000should serve up your app correctly.

nginx

Intro

Nginx is a high performance server and reverse proxy. Reverse proxying means that when a visitor hits a website, nginx will retrieve webpages and other resources from multiple servers, and present it as if it came from the server itself. This allows you to have multiple, distributed servers to handle more visitors.

Installation

sudo apt-get install nginx

The nginx service should automatically start. If it doesn't:

sudo service nginx start

When you visit http://your-public-ip, you should now see the default nginx configuration (If you see this page, the nginx web server is successfully installed and working. Further configuration is required.)

Let's do some configuring.

Configuration

cd into /etc/nginx/sites-available. You should see a file named default. I modified my default file to return 404 for all websites, instead of displaying the default nginx config.

contents of default (optional):

server {
    return 404;
}

Create a new file with the name of your (sub)domain that you want to host your project on: e.g. /etc/nginx/sites-available/hackit.edward.io.

Here's the contents of a basic config for our Flask app. This should be in /etc/nginx/sites-available/my-domain.com (my-domain.com is a file, not a folder):

server {
  listen 80;
  server_name hackit.edward.io;
  root /my/project/folder;

  access_log /my/project/folder/access.log;
  error_log /my/project/folder/error.log;

  location / {
    proxy_pass http://127.0.0.1:8000;
  }
}

Make sure to modify the server_name to point your own domain, and your root to your project and logs to wherever you want your logs to be stored.

Let's test our config to see if it has valid syntax: sudo nginx -t. Hopefully the test should be successful.

Now we need to symlink our configuration file:

ln -s /etc/nginx/sites-available/my-domain.com /etc/nginx/sites-enabled/my-domain.com`

Now we need to reload nginx to make our new configuration active:

sudo service nginx reload

Try accessing your domain specified at server_name. You should see your Flask app appear, congratulations! You've reached the end of the tutorial.


Things don't work

Some problems I've personally ran into while setting up my flask app with nginx and gunicorn: hopefully this will help you.

Gunicorn problems

The gunicorn command must be running at all times. You can keep it running by using nohup:

nohup gunicorn myfile:app -b 0.0.0.0:8000

This allows gunicorn to run in the background, even if you exit your terminal session. However, if you reboot your server or if gunicorn runs into a problem, your web app may stop working.
The best way to run gunicorn is to run it as a daemon. I'll write a blog post about this in the future.

Nginx problems

Nginx and apache2 cannot run at the same time on the same port. If you have trouble starting up nginx, make sure you don't have anything else binding to the 80port.

DNS problems

Is the default nginx page not showing up?

Your domains might not be setup correctly. Check your NS records. Make sure your CNAME or A records for your domains are pointing to the right server.


Thanks to Brandon Wu for helping me with setup.