Python hosting

Mythic Beasts hosting accounts fully support hosting Python applications. While Python scripts can be hosted as simple CGI scripts, this rarely gives acceptable performance, so this page explains how to setup a persistent Python application server.

To use these instructions you will need a hosting account with the shell access option.

Overview

Our Python hosting solution works by instructing our web server to pass requests for you website to a Unix domain socket. You can then configure a web server running your Python application to listen on that socket. Listening on a Unix domain socket is very much like listening on a specified port number on localhost, but it has the advantage that the socket can be secured by Unix file permissions, meaning that only you can listen for requests on that socket.

For this documentation we will use the Gunicorn server to run a simple Flask application, but this approach is not tied to this software, or even to Python; you can use it to handle requests for your website using any process that can process HTTP requests over a Unix domain socket.

Creating an application

If you already have a WSGI application that you wish to host, you can skip this section. You will need to know the name of the module containing the WSGI application, and the name of the application within that module.

Flask is installed on our hosting servers via system packages, but most users prefer to use a Python virtual environment, so that they can have full control over which packages and versions are used.

To create and activate a virtual environment, run the following commands:

python3 -mvenv ~/venv
. ~/venv/bin/activate

You can now install Flask and Gunicorn into the virtualenv:

pip3 install flask gunicorn

Now create a trivial Flask application:

mkdir ~/myflaskapp

Create the file hello.py in the myflaskapp directory with the following content:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

Starting Gunicorn

We can now start gunicorn, but we need to specify the name of the Unix socket to listen on. This will be a file called proxy.sock inside the web root for your website. If your website is www.example.com this will be:

$HOME/www/www.example.com/proxy.sock

If you put the name of your website into an environment variable called VHOST you can simply copy and paste later instructions:

VHOST=www.example.com

To start Gunicorn, run the following:

cd $HOME/myflashapp
gunicorn -b unix:$HOME/www/$VHOST/proxy.sock hello:app

This should output a few lines of output, including one log message that says [INFO] Listening at ... and the name of the socket.

Now that Gunicorn is listening for requests on the Unix socket we need to tell the web server to proxy requests to this socket. Either open a second terminal session on the server, or press Ctrl+C to stop gunicorn for now.

Configuring the web server

To instruct the web server to send requests to the Unix domain socket, create a file called .proxy-to-socket in the same directory as the proxy.sock file. This can be an empty file:

touch $HOME/www/$VHOST/.proxy-to-socket

The web server configuration is reloaded once every five minutes, so you may need to wait for up to five minutes for the above to take effect. If you stopped Gunicorn, restart it now with the same command as before.

Open a web browser, and browse to your site. Once the web server has reloaded, you should see requests being handled by Gunicorn, and should see the simple "Hello World!!" output from the Flask application.

This should have given you a working Python application hosting your website, but it will only continue to work for as long as Gunicorn is running.

You can make Gunicorn run in the background with the -D command line option, but this won't restart the process if the hosting server is rebooted.

Making Gunicorn persistent

We can use a systemd user service to ensure that Gunicorn is started when the server boots. To do this we need to create a service config file. First create this directory:

mkdir -p $HOME/.config/systemd/user

Now create a file in that directory called myflaskapp.service with the following contents. Note that you will need to manually replace VHOST with the name of your website:

[Unit]
Description=My Flask Application

[Service]
Type=simple
StandardOutput=journal
Environment=PYTHONPATH=%h/myflaskapp
ExecStart=%h/venv/bin/gunicorn3 -b unix:%h/www/VHOST/proxy.sock hello:app
Restart=always
RestartSec=1

[Install]
WantedBy=default.target

Now run the following:

systemctl --user daemon-reload
systemctl --user start myflaskapp

Neither command should produce any output. You can check if Gunicorn has been started by running:

journalctl --user

This should show log output from Gunicorn.

Finally, you need to make sure that your myflaskapp service starts on boot. You can do this with:

systemctl --user enable myflaskapp

Running a different application

To run an application other than our trivial hello.py Flask application, you will need to adjust the hello:app parameter to the Gunicorn process.

Note that whenever you make changes to the service file, you will need to run:

systemctl --user daemon-reload
systemctl --user restart myflaskapp

(note that myflaskapp corresponds to the name of the service file, so you can name this something different if you prefer)

To run your application, you can either install your application into your virtualenv, or adjust the Environment line in the service file so that the directory containing your application is on the Python path.

The first option is usually simplest, and is normally done by making sure that you virtualenv is active, and then running python3 setup.py install from the directory containing your application.

With this done, the Environment line in the service file is unnecessary.

The parameter to the Gunicorn process (the hello:app bit) is the name of the module containing your application, and the name of the variable or function in that module that provides the application. app or create_app are customary for flask applications.