Docs
Applications
Flask

No Cache

Flask by default caches files on client devices. When in development, this is a goddamn headache. Especially when you're writing CSS. Unlike VSCode's live reload. Syncing js and css changes require you to do a hard reload using ctrl + f5. The code below counters this by specifying in the response header to not save anything.

# don't fucking save assets in cache
@app.after_request
def add_header(r):
    r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
    r.headers["Pragma"] = "no-cache"
    r.headers["Expires"] = "0"
    r.headers["Cache-Control"] = "public, max-age=0"
    return r

Deployment

  1. Setup database
  2. Create systemd service for gunicorn
  3. Create hook instance config json
  4. Configure GitHub repo's webhook settings
  5. Create a group for the hook service
  6. Create a systemd service for the hook instance
  7. Write the pull and reload script

Connecting a DB

First create a separate database and user for the web-app in the usual way. Then reference the db from the flask app. To get MariaDB working on SQLAlchemy you'll also need to install the pymysql package1.

app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://<username>:<password>@localhost/<app-dbname>?charset=utf8mb4"

Gunicorn Service

To run a flask app in development you need a WSGI HTTP Server to serve and run it. This is where gunicorn comes in.

Development

When in development you can run gunicorn in the shell. The <app-name> refers to the file name of the flask app and the app refers to the name of the callable within it.

# run it on port 5000
gunicorn --bind localhost:5000 <app-name>:app

Production

When deploying, create a systemd service for the gunicorn. See more info about creating systemd services here.

Find info about workers and optimizations here (opens in a new tab).

Here's an example systemd config for gunicorn.

[Unit]
Description=Gunicorn instance serving the app
After=network.target
 
[Service]
User=<reg_user>
Group=www-data
WorkingDirectory=/home/<reg_user>/project
ExecStart=/usr/local/bin/gunicorn --workers 3 --bind localhost:5000 <app-name>:app
 
[Install]
WantedBy=multi-user.target

Hook Instance

Create a webhook instance json file and connect it with Github to listen to push requests from the repo. More info here.

Then create a new group for the flask app and couple it with the already existing webhook user created when setting up the webhook application. In it whitelist the systemctl commands for dealing with the gunicorn service.

Write a systemd service file with the webhook user and the new group for the hook instance. Find out how here.

Integration Script

Since systemd runs services in isolated environments. Without access to the user shell nor any environment variables. So, for private repos an ssh-agent needs to be started every time the script is run for Github authentication. More info here.

First clean the repo of any untracked changes. Then pull the latest commit. Find how here. Finally reload the site. Make sure to only use the systemctl commands that were defined in the group the hook instance is running on.

systemctl restart <app-service>

Footnotes

  1. https://stackoverflow.com/questions/54834088/python-database-connection-for-mariadb-using-sqlalchemy (opens in a new tab) ↩