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:
- It runs every day at 09:17 UTC (cron
17 9 * * *). GitHub runs cron schedules in UTC. - It sends three real SELECTs per run, so the database sees a few requests each day.
- Every request uses
curl --retry 5 --retry-all-errors --retry-delay 15, so one DNS blip does not fail the run. - 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
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
- Supabase: Project pausing
- GitHub Docs: Events that trigger workflows, schedule
- Autotube repo: supabase-keepalive.yml and PROGRESS.md @supabase_io @clickit_devops @mysql_postgres_24002ebe72
Top comments (0)