2 minute read

Problem

Streamlit cloud allows developers to easily host streamlit applications for free! However, public apps will eventually hibernate if unused for a period of time. This can result in an pesky and occasionally lengthy reboot time. See details

placeholder

Solution

To make sure the app is always primed, I automated a keep alive probe which opens the website and clicks on the “reset” button if visible. I leveraged puppeteer to navigate and interact with the webpage using a headless browser. Then I automated a cron-job using Github Actions.

Creating a Probe Script

First, create the probe script to poke the website and look for the relevant “wake-up” button:

// probe-action/probe.js

const puppeteer = require('puppeteer');
const TARGET_URL = "https://<YOUR_STREAMLIT_APP_URL>.streamlitapp.com/";
const WAKE_UP_BUTTON_TEXT = "app back up";
const PAGE_LOAD_GRACE_PERIOD_MS = 8000;

(async () => {
    const browser = await puppeteer.launch(
        { args: ["--no-sandbox"] }
    );

    const page = await browser.newPage();
    await page.goto(TARGET_URL);
    // Wait a grace period for the application to load
    await page.waitForTimeout(PAGE_LOAD_GRACE_PERIOD_MS);

    const checkForHibernation = async (target) => {
        // Look for any buttons containing the target text of the reboot button
        const [button] = await target.$x(`//button[contains(., '${WAKE_UP_BUTTON_TEXT}')]`);
        if (button) {
            console.log("App hibernating. Attempting to wake up!");
            await button.click();
        }
    }

    await checkForHibernation(page);
    const frames = (await page.frames());
    for (const frame of frames) {
        await checkForHibernation(frame);
    }

    await browser.close();
})();

Dockerize

Next, create a very simple dockerfile from an existing puppeteer image. Its only job is to run the probe script:

# probe-action/Dockerfile
FROM ghcr.io/puppeteer/puppeteer:17.0.0
COPY . /home/pptruser/src
ENTRYPOINT [ "/bin/bash", "-c", "node -e \"$(</home/pptruser/src/probe.js)\"" ]

You can test it locally like so:

docker build -t probe:latest .
docker run -it --rm probe:latest

Schedule a cron-job with Github Actions

Now we’ll setup the github action and cron-job to run this probe routinely.

The final repository for the streamlit app looks like this:

repo/                   # The git repo deployed to streamlit cloud
  .github/
    workflows/
      keep-alive.yml    # The cron job
  probe-action/         # The keep-alive action
    Dockerfile
    action.yml
    probe.js
  app.py                # The streamlit app

Start by creating the action metadata file which Github Actions will use to build the docker image and run the script.

# probe-action/action.yml
name: "Probe Deployed App"
description: "Probes a deployed streamlit app."
runs:
  using: "docker"
  image: "Dockerfile"

Finally, create a workflow file .github/workflows/keep-alive.yml. Every 2 days it will check out the repo and run the probe action.

# .github/workflows/keep-alive.yml
name: Trigger Probe of Deployed App on a CRON Schedule
on:
  schedule:
    # Runs "at minute 0 past every 48 hour" (see https://crontab.guru)... ie: every 2 days
    - cron: '0 */48 * * *'

jobs:
  probe_deployed_app:
    runs-on: ubuntu-latest
    name: A job to probe deployed app
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Probe Deployed App Action Step
        uses: ./probe-action # Uses an action in the probe-action directory
        id: probe

Comments