Deploy your Flask Web Application on Ubuntu 16.04 with Apache, Gunicorn and systemd

Mon 10 October 2016

I still get questions from time to time about how to deploy a python web application using Apache and not NGINX. Here is a quick tutorial to deploy your Flask application on Ubuntu 16.04 or any linux distribution (considering relevant changes) using Apache, Gunicorn and systemd. Until some weeks ago I used supervisord instead of systemd but nowadays I prefer to use systemd because is already there, installed, part of system. And also the reason to look into systemd and to switch was that I had to deploy an application on SLES (SUSE Linux Enterprise Server) and there is no supervisord package available in repos.

Note: This is a very basic configuration to get everything running. It is just for learning and to get the idea how everything is connected.

So, let's start:

  • we create a user which will run our Flask application
# adduser flaskappuser
  • install and configure apache:
# apt-get install apache2

As result we should get the default apache webpage in browser (http://your-ip-here)

  • we have to enable proxy modules for apache:
# a2enmod

and give this list of modules to enable:

proxy proxy_ajp proxy_http rewrite deflate headers proxy_balancer proxy_connect proxy_html
  • Add our application to apache web server config file. Add the following lines (inside VirtualHost block) to /etc/apache2/sites-available/000-default.conf. Make a backup of this file before you modify it
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPreserveHost On
    <Location "/flaskapp">
          ProxyPass "http://127.0.0.1:5000/"
          ProxyPassReverse "http://127.0.0.1:5000/"
    </Location>

so, the final file should look like this:

<VirtualHost *:80>
    # The ServerName directive sets the request scheme, hostname and port that
    # the server uses to identify itself. This is used when creating
    # redirection URLs. In the context of virtual hosts, the ServerName
    # specifies what hostname must appear in the request's Host: header to
    # match this virtual host. For the default virtual host (this file) this
    # value is not decisive as it is used as a last resort host regardless.
    # However, you must set it for any further virtual host explicitly.
    #ServerName www.example.com

    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html

    # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
    # error, crit, alert, emerg.
    # It is also possible to configure the loglevel for particular
    # modules, e.g.
    #LogLevel info ssl:warn

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    # For most configuration files from conf-available/, which are
    # enabled or disabled at a global level, it is possible to
    # include a line for only one particular virtual host. For example the
    # following line enables the CGI configuration for this host only
    # after it has been globally disabled with "a2disconf".
    #Include conf-available/serve-cgi-bin.conf
    <Proxy *>
        Order deny,allow
          Allow from all
    </Proxy>
    ProxyPreserveHost On
    <Location "/flaskapp">
          ProxyPass "http://127.0.0.1:5000/"
          ProxyPassReverse "http://127.0.0.1:5000/"
    </Location>
</VirtualHost>
  • restart apache to see if is working:
# service apache2 restart

http://your-ip-here —> should give you the same standard html page for apache, as before

http://your-ip-here/flaskapp —> should give you:

Service Unavailable
The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.

that’s because we still don’t have our Flask app running, but it seems that apache is trying to send the request to it, good.

  • let’s take care of our Flask app:

we will run it in a virtual environment, so let’s install virtualenv:

# apt-get install python3-venv

create and activate our new venv:

# cd /home/flaskappuser
# mkdir flaskapp
# cd flaskapp
# python3.5 -m venv flaskvenv
# source flaskvenv/bin/activate

install flask and gunicorn in our venv:

# pip install flask gunicorn

create out simple flask app (/home/flaskappuser/flaskapp/app.py)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/env python

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello from FLASK"

if __name__ == "__main__":
    app.run(host='127.0.0.1')

let’s run our app to see if is working:

(flaskvenv) root@apache-flask:~/flaskapp/# python app.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [10/Oct/2016 13:58:56] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [10/Oct/2016 13:58:59] "GET / HTTP/1.1" 200 -

in your web browser you should see: Hello from FLASK if you try: http://your-ip-here/flaskapp

  • everything is ok right now, but we want to use Gunicorn as our application server, so let’s configure it

in the same place where your Flask application is (/home/flaskappuser/flaskapp/ in my case) create a gunicorn.conf file with the following content:

accesslog = "/home/flaskappuser/flaskapp/logs/gunicorn_access.log"
errorlog = "/home/flaskappuser/flaskapp/logs/gunicorn_error.log"

we have to create our directory where to store the logs files:

# mkdir logs

now we can try to test our app again by running it using gunicorn:

# gunicorn -c gunicorn.conf -b 0.0.0.0:5000 app:app

check the browser and see if you app is working. Should work 😃

make sure that everything in flaskappuser home directory belongs to this user

# chown -R flaskappuser:flaskappuser /home/flaskappuser/

Now we have one more step. We want to monitor our Flask app and to restart it on crashing or to have nice start/stop commands for it. Or to have it started automatically on reboot. In order to do that we can use systemd which is available already in Ubuntu 16.04.

For that we have to create a .service file for our app. Here is my file: (/etc/systemd/system/flaskapp.service):

[Unit]
Description=flaskapp
After=network.target

[Service]
User=flaskappuser
Restart=on-failure
WorkingDirectory=/home/flaskappuser/flaskapp/
ExecStart=/home/flaskappuser/flaskapp/flaskvenv/bin/gunicorn -c /home/flaskappuser/flaskapp/gunicorn.conf -b 0.0.0.0:5000 app:app

[Install]
WantedBy=multi-user.target

activate our .service file

# systemctl daemon-reload

enable it at boot/restart

# systemctl enable flaskapp

start our app

# systemctl start flaskapp

Check if our app is running:

(flaskvenv) root@apache-flask:~/flaskapp# tail -f /var/log/syslog
Oct 10 14:25:59 guest systemd[1]: Started ACPI event daemon.
Oct 10 14:26:03 guest systemd[1]: Started flaskapp.
(flaskvenv) root@apache-flask:~/flaskapp# ps aux | grep gunicorn
flaskappuser      7263  0.2  2.9  64904 22492 ?        Ss   14:26   0:00 /home/flaskappuser/flaskapp/flaskvenv/bin/python3.5 /home/flaskappuser/flaskapp/flaskvenv/bin/gunicorn -c /home/flaskappuser/flaskapp/gunicorn.conf -b 0.0.0.0:5000 app:app

check the app in your browser:

http://your-ip-here/flaskapp

you can stop your app with:

# systemctl stop flaskapp

Done!

Note: Here is my repository with the files: GitHub