This howto assumes that you

  • created your ec2 already with Amazon Linux on it (I personally use lightsail as have great prices and IMO are a bit cheaper than ec2 instances, but there’s no difference on OS level)
  • opened port 80 (http) and 22 (ssh)

Flask

Deploy your flask app into /home/ec2-user/app. For simplicity let’s put the following into /home/ec2-user/app/app.py:

from flask import Flask
application = Flask(__name__)

@application.route("/")
def index():
    return "Nobody expects the spanish inquisition!"

if __name__ == "__main__":
    application.run(host='0.0.0.0', port='8080')

Let’s use virtualenv (important especially if you run more than just this one webapp)

The following does:

  • create the venv folder
  • make the current shell use the venv folder (exit again with deactivate)
  • install flask and gunicorn into venv
  • store the used pip package versions into requirements.txt

Run this in ~/app/:

virtualenv -p python3 venv
source venv/bin/activate
pip install flask
pip install gunicorn
pip freeze > requirements.txt

Now, python app.py should output * Running on http://127.0.0.1:8080

Gunicorn / systemd

While still in the venv, run gunicorn:

gunicorn app

This should output [INFO] Listening at: http://127.0.0.1:8000

You usually want your gunicorn process to start at boot time. Also you would like to be able to restart it. For this use systemd:

put this into /etc/systemd/system/flaskapp.service:

[Unit]
Description=Gunicorn daemon to serve my flaskapp
After=network.target
[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory=/home/ec2-user/app
ExecStart=/home/ec2-user/app/venv/bin/gunicorn app
[Install]
WantedBy=multi-user.target

Note that this starts the gunicorn executable within the venv directory - which itself uses the python with all required pip packages.

To start it:

sudo systemctl start flaskapp

Check if it started without errors:

sudo systemctl status flaskapp

To start the service on linux boot time (it’s started after network becomes available, because of the After=network.target line):

sudo systemctl enable flaskapp

gunicorn now runs at 127.0.0.1:8000 - that’s where we need to point nginx to.

Nginx

On Amazon Linux, nginx cannot be installed with sudo yum install nginx but instead with:

sudo amazon-linux-extras install nginx1

Now start it and make it start at linux boot time:

sudo systemctl start nginx
sudo systemctl enable nginx

To install the reverse proxy, create /etc/nginx/conf.d/app.conf (this is loaded because /etc/nginx/nginx.conf contains this line: include /etc/nginx/conf.d/*.conf;):

server {
    listen 80 default_server;
    server_name _;
    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        proxy_pass         http://127.0.0.1:8000/;
        proxy_redirect     off;

        proxy_set_header   Host                 $host;
        proxy_set_header   X-Real-IP            $remote_addr;
        proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto    $scheme;
    }
}

The config is taken from the flask documentation which also explains the proxy_set_header lines.

To test if the config is correct: sudo nginx -t and if there are no errors you can reload: sudo systemctl reload nginx.

Now, to check that nginx can connect to the gunicorn socket check error.log:

sudo tail -F /var/log/nginx/error.log

Now you should be able to see your app on http://public_ip_address

And then…

  • did changes to your app? Store the new files on ec2 and then run sudo systemctl restart flaskapp to make gunicorn refresh the webpage
  • an alternative to the http forward would be forwarding by socket, but that proved to be more error prone. If you think that sockets are more performant, they’re not, see this stackoverflow answer.