DEV Community

Muhammad Hamza
Muhammad Hamza

Posted on

My Supabase keepalive was working. The project paused anyway.

I am rebuilding Autotube, my open-source app that turns a topic into a YouTube video, on free tiers only. The frontend is on Vercel, the database is Supabase, files go to Cloudflare R2, and the heavy video jobs will run on GitHub Actions.

Supabase pauses free projects that look inactive. So in September I added a small GitHub Actions workflow to keep the database awake. It ran on time, every run did a real database query, and the project paused anyway.

The short version: Supabase looks at how much traffic a free project gets across a whole week, and one query every five days was too little. Below is what I got wrong, and what the workflow looks like now.

The first version: one query every five days

I read the pausing rule as a 7-day timer. One request inside 7 days resets it, so a ping every 5 days should be safe.

The workflow ran at 09:00 UTC every fifth day of the month (cron 0 9 */5 * *). That fires on days 1, 6, 11, 16, 21, 26 and 31, so the longest gap is 5 days, even across the end of a month. Each run did one SELECT through PostgREST on a one-row table called _keepalive. The table has RLS on and a policy that lets the anon key read it, so the workflow only needs the public URL and the anon key as repo secrets.

I ran it by hand on 14 September, got HTTP 200, and moved on.

What happened: two failed runs and a paused project

The run history had five runs in total:

Date Result
14 Sep success (manual)
16 Sep success
21 Sep success
26 Sep failed: curl: (6) Could not resolve host
1 Oct failed: same error

The project paused around 28 September, one week after a successful query on the 21st. I found out on 3 October, when I opened the dashboard and had to resume it by hand. My notes from that day are in the project's progress log.

Two things were wrong.

The first was my reading of the rule. The Supabase docs on project pausing say they pause "Free Plan projects that show low activity over a 7-day period", and that "typically a few user requests to the database each day over the previous week is enough" to stay up. So Supabase looks at how much traffic the project got across the whole week. One query every 5 days is too little. A 3-day schedule would have failed the same way.

The second was that the failure was silent. Both failed runs showed a red X in the Actions tab, and I did not open that tab until October. Also, a DNS error looks like a network blip. Here it meant the project was already asleep, because a paused project stops resolving.

The fix: daily pings, retries and an alert

The new workflow does four things differently:

  1. It runs every day at 09:17 UTC (cron 17 9 * * *). GitHub runs cron schedules in UTC.
  2. It sends three real SELECTs per run, so the database sees a few requests each day.
  3. Every request uses curl --retry 5 --retry-all-errors --retry-delay 15, so one DNS blip does not fail the run.
  4. A second job opens a GitHub issue when the ping fails, with the triage steps written inside it.

The odd minute is on purpose. GitHub's docs on the schedule event say scheduled runs "can be delayed during periods of high loads", that high load includes "the start of every hour", and that "some queued jobs may be dropped". A keepalive that gets dropped at 09:00 is the same problem again, so mine runs at 09:17.

The ping step, from the full workflow file:

- name: Query _keepalive table (3x)
  run: |
    set -euo pipefail
    for i in 1 2 3; do
      status=$(curl -sS -o /tmp/body.json -w "%{http_code}" \
        --retry 5 --retry-all-errors --retry-delay 15 \
        "$SUPABASE_URL/rest/v1/_keepalive?select=id,pinged_at&limit=1" \
        -H "apikey: $SUPABASE_ANON_KEY" \
        -H "Authorization: Bearer $SUPABASE_ANON_KEY")
      echo "ping $i: HTTP $status"
      if [ "$status" != "200" ]; then exit 1; fi
      sleep 2
    done
Enter fullscreen mode Exit fullscreen mode

The alert job runs only on failure. It reuses one open issue with a supabase-keepalive label and adds a comment, so a week of failures gives me one issue with seven comments and my inbox stays usable. The issue text tells future me what each error means. A DNS error means the project is paused, so resume it from the dashboard and re-run the workflow. A 401 or 403 means the anon key changed, so update the repo secret.

After I resumed the project, I triggered the new workflow by hand. All three pings came back HTTP 200: https://github.com/Hamzaakhtar87/autotube/actions/runs/37121614109

The risk I still have: GitHub's 60-day rule

GitHub has its own sleep rule. In a public repository, "scheduled workflows are automatically disabled when no repository activity has occurred in 60 days." So the job that keeps Supabase awake can be put to sleep too, by GitHub, if I stop pushing to the repo for two months.

While I am building, that will not happen. If Autotube ever sits untouched, I need a paid plan or a pinger that lives outside GitHub.

What I would tell someone on free tiers

Read what "activity" means in the provider's own words before you design around it. My keepalive did exactly what I told it to do. I just told it the wrong thing.

And make the cron fail loud. A scheduled job with no alert is one you only check after something breaks, which is how a red X sat in my Actions tab for a week.

Sources

Top comments (0)