<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhammad Hamza</title>
    <description>The latest articles on DEV Community by Muhammad Hamza (@muhammad_hamza_0e80c384ca).</description>
    <link>https://dev.to/muhammad_hamza_0e80c384ca</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4205320%2F86245b46-1b29-4c0e-8ff4-47edf0d3e9fc.png</url>
      <title>DEV Community: Muhammad Hamza</title>
      <link>https://dev.to/muhammad_hamza_0e80c384ca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhammad_hamza_0e80c384ca"/>
    <language>en</language>
    <item>
      <title>My Supabase keepalive was working. The project paused anyway.</title>
      <dc:creator>Muhammad Hamza</dc:creator>
      <pubDate>Sat, 24 Oct 2026 18:55:49 +0000</pubDate>
      <link>https://dev.to/muhammad_hamza_0e80c384ca/my-supabase-keepalive-was-working-the-project-paused-anyway-6m0</link>
      <guid>https://dev.to/muhammad_hamza_0e80c384ca/my-supabase-keepalive-was-working-the-project-paused-anyway-6m0</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first version: one query every five days
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The workflow ran at 09:00 UTC every fifth day of the month (cron &lt;code&gt;0 9 */5 * *&lt;/code&gt;). 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 &lt;code&gt;_keepalive&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;I ran it by hand on 14 September, got HTTP 200, and moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened: two failed runs and a paused project
&lt;/h2&gt;

&lt;p&gt;The run history had five runs in total:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;14 Sep&lt;/td&gt;
&lt;td&gt;success (manual)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 Sep&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21 Sep&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26 Sep&lt;/td&gt;
&lt;td&gt;failed: &lt;code&gt;curl: (6) Could not resolve host&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 Oct&lt;/td&gt;
&lt;td&gt;failed: same error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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 &lt;a href="https://github.com/Hamzaakhtar87/autotube/blob/main/PROGRESS.md" rel="noopener noreferrer"&gt;project's progress log&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two things were wrong.&lt;/p&gt;

&lt;p&gt;The first was my reading of the rule. The &lt;a href="https://supabase.com/docs/guides/platform/free-project-pausing" rel="noopener noreferrer"&gt;Supabase docs on project pausing&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: daily pings, retries and an alert
&lt;/h2&gt;

&lt;p&gt;The new workflow does four things differently:&lt;/p&gt;

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

&lt;p&gt;The odd minute is on purpose. &lt;a href="https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule" rel="noopener noreferrer"&gt;GitHub's docs on the schedule event&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;The ping step, from the &lt;a href="https://github.com/Hamzaakhtar87/autotube/blob/main/.github/workflows/supabase-keepalive.yml" rel="noopener noreferrer"&gt;full workflow file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The alert job runs only on failure. It reuses one open issue with a &lt;code&gt;supabase-keepalive&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;After I resumed the project, I triggered the new workflow by hand. All three pings came back HTTP 200: &lt;a href="https://github.com/Hamzaakhtar87/autotube/actions/runs/37121614109" rel="noopener noreferrer"&gt;https://github.com/Hamzaakhtar87/autotube/actions/runs/37121614109&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The risk I still have: GitHub's 60-day rule
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell someone on free tiers
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Supabase: &lt;a href="https://supabase.com/docs/guides/platform/free-project-pausing" rel="noopener noreferrer"&gt;Project pausing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Docs: &lt;a href="https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule" rel="noopener noreferrer"&gt;Events that trigger workflows, schedule&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Autotube repo: &lt;a href="https://github.com/Hamzaakhtar87/autotube/blob/main/.github/workflows/supabase-keepalive.yml" rel="noopener noreferrer"&gt;supabase-keepalive.yml&lt;/a&gt; and &lt;a href="https://github.com/Hamzaakhtar87/autotube/blob/main/PROGRESS.md" rel="noopener noreferrer"&gt;PROGRESS.md&lt;/a&gt;
&lt;a class="mentioned-user" href="https://dev.to/supabase_io"&gt;@supabase_io&lt;/a&gt; &lt;a class="mentioned-user" href="https://dev.to/clickit_devops"&gt;@clickit_devops&lt;/a&gt; &lt;a class="mentioned-user" href="https://dev.to/mysql_postgres_24002ebe72"&gt;@mysql_postgres_24002ebe72&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automation</category>
      <category>database</category>
      <category>github</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
