Supalive

Documentation

1 min read·0% read

Installing Supalive

Supalive pings your Supabase projects on a schedule via GitHub Actions, so free-tier projects don't get paused for inactivity. It has no server and no dependency beyond GitHub itself.

The fast path: hand it to an AI assistant

Every download includes AI-SETUP.md — a self-contained prompt written for an AI coding assistant (Claude, ChatGPT, Copilot, Cursor, or anything similar). Open the setup app, add your projects, download the ZIP, then paste AI-SETUP.md into your assistant along with the rest of the unzipped folder. It walks the assistant through creating a repository, pushing the files, and adding the right GitHub secrets — the one part of setup that can't be automated away, and the part people most often get stuck on.

This is the recommended path, not a fallback. The GitHub half of setup is mechanical and exactly the kind of thing an assistant you already use can do correctly in one pass.

The manual path

  1. Open the setup app and add each Supabase project you want kept alive: a name, its URL, the anon or publishable key (used to verify each project, never stored), and the name of any table in your schema — it doesn't matter which one, the ping only needs to reach Postgres, not fetch anything meaningful. A link beside the field opens your project's table editor if you want to check a name first.
  2. Download the ZIP. It contains the workflow, your projects.json, a README specific to your setup, and a personalized AI-SETUP.md.
  3. Create a private GitHub repository and push the unzipped contents. The generated README includes a one-line gh repo create ... --push command if you have the GitHub CLI.
  4. Add each secret the setup app lists — one GitHub Actions secret per project, named exactly as shown, valued with that project's anon/publishable key.
  5. Trigger a manual run from the Actions tab and confirm every project shows alive in the step summary.

From here it runs on its own, twice a day, with no further action needed.

Restoring or moving your setup

  • Resume link — the setup app can encode your current project list (names, URLs, endpoints, secret names — never keys) into a URL. Open that link on another machine to restore your list.
  • Import — if you already have a projects.json (from a fresh clone, or a machine that lost its browser storage), paste its contents into the Import section to restore the UI from it. Duplicates are skipped.

Changing the schedule

Edit the cron expression at the top of .github/workflows/supalive.yml — it ships set to 0 8,20 * * * (08:00 and 20:00 UTC). You can also trigger a run manually anytime from the Actions tab.

Features

Pick any table — it doesn't matter which

The ping exists to make Postgres do something, not to fetch data that means anything. Any table in your project that the anon role can reach keeps the database awake, whether that's your busiest table or one you haven't touched in months. There's no "right" table to find, so the setup app doesn't ask you to find one — type any name you recognize from your own schema.

A link beside the field ("see your tables ↗") opens your project's table editor in a new tab, so checking a name takes a copy-paste, not a lookup. Manual entry is also more reliable than it sounds: a table with Row Level Security on and no policy for the anon role still returns 200 with an empty list — the request reaches Postgres, which is the whole point — and only a table that doesn't exist at all returns 404. In practice, almost any real table name works.

Table discovery runs quietly in the background and stays out of the way. If your project still lets an anon/publishable key list its tables — most current Supabase projects don't — the setup app picks that up and offers the results as autocomplete suggestions on the table field. You won't see a message either way: no toast when it finds something, none when it doesn't. It's a shortcut when it fires, never something you need to notice or respond to. The key it uses is never stored.

Real ping test, not a guess

Before a project is added, the setup app fires the exact request the workflow will make — same headers, same endpoint — and shows the real result.

  • A real HTTP status that isn't 2xx blocks adding the project, with the specific reason (a 404 means the table genuinely doesn't exist).
  • A request that fails with no status at all — a CORS rejection, being offline, a restrictive proxy — is a different situation: that's a property of your browser's network path, not evidence the project is broken. The setup app warns rather than blocking outright, and lets you add the project anyway. This mirrors how the workflow itself treats unused secrets: reported, never treated as a hard failure on its own.

This is what makes typing any table name safe rather than risky: nothing gets added until the exact ping the workflow will run has already succeeded once.

Failures that say what to do about them

A ping can fail for reasons that look identical in a status code and demand completely different responses. A rotated key, a dropped table, a paused project and a Supabase outage are four different problems, and "failed" describes all of them equally badly.

Every failure is sorted into one of two verdicts, stated in one line at the top of the job summary before any detail:

  • Action required — something in your repository or your project needs changing. A rejected key, a table that no longer exists, a missing secret, a paused project, a billing restriction, a URL that doesn't resolve.
  • Outage — Supabase is not answering and nothing you own is misconfigured. The next scheduled run will try again on its own.

Both fail the run, because a database you're trying to keep awake being unreachable is worth a notification either way. The difference is that you can tell from the first line whether to open your dashboard or ignore it.

Each failed project then gets its own section carrying four things: what happened, what Supabase itself said, what to do about it, and a log of each attempt with its status and timing. The response body is read rather than discarded, so when PostgREST says relation "public.leaderboard" does not exist, that is the sentence you see — not a bare 404.

Platform-specific codes are decoded, not passed through. Supabase uses 540 for a paused project, 402 for a project restricted under the Fair Use Policy, and 544 for a gateway timeout. None of these mean anything without a lookup, so the summary explains them in place.

Transport failures are diagnosed too. A request that never gets an HTTP response carries curl's exit code, which is often more informative than a status would have been — a DNS failure means the project was deleted or the URL has a typo, and says so, rather than reporting a meaningless 000.

Retries apply only where they can help. A project answering 503 gets three attempts across about a minute, since a service restarting underneath the gateway often clears in seconds. A rejected key or a missing table is not retried at all, because it will answer identically however long you wait.

One thing worth knowing: Supalive cannot revive a project that has already been paused. A paused project is refused at the gateway, so the ping never reaches Postgres. If you ever see the paused verdict, the pings had been failing for some time beforehand — the earlier runs will show it.

Resume link and import

Two different ways to move your project list between machines or browser sessions:

  • Resume link encodes your current list (names, URLs, endpoints, secret names — never keys) into a URL. Open it anywhere to restore the list.
  • Import accepts a pasted projects.json, for when you already have one — a fresh clone of your repo, or a machine that lost its browser storage. Duplicates are skipped.

Your project list is also saved to the browser's local storage automatically, so closing the tab doesn't lose it.

Personalized ZIP packaging

"Download ZIP" assembles a complete, ready-to-push package: the workflow file, your projects.json, a README naming your exact secrets, and a personalized AI-SETUP.md listing your specific projects. Everything is generated in the browser — nothing is uploaded anywhere.

No server, no account

The setup app has no backend. Everything — verifying a project, building your ZIP — runs in your browser, so there is no server for your details to be stored on and no account to create. The only network request it makes is to your own Supabase project, to check the table you picked actually answers.

Built-in help

A ? button in the top corner opens a short overview — what Supalive does, the four steps from adding a project to it running on its own, and a plain answer to "is this safe?". It opens automatically the first time you visit and stays out of the way after that; reopen it anytime from the same button. Nothing about using this page assumes you've read the docs first.

FAQ

Is it safe to put my Supabase anon key in a GitHub secret?

Yes — and it's safer there than most places it already lives. Anon and publishable keys are designed to be public: they ship inside every browser bundle of every Supabase app you build, visible to anyone who opens dev tools. They carry no authority beyond whatever Row Level Security grants the anon role. Storing it as a GitHub Actions secret isn't there to protect a dangerous credential — it's so your repository stays shareable and your project URLs don't sit in plain text where casual scrapers pick them up.

Supalive never asks for, stores, or transmits a service role key. There's nowhere in the workflow or the setup app that key would even be used.

What does the setup app actually send, and where?

Your anon/publishable key is sent to exactly one place: your own Supabase project — once in the background to check whether it can list tables (silently; you won't see this happen either way), and once to verify the table you typed actually works before adding the project. It's never stored — not in localStorage, not in the generated files. Nothing you type into the setup app is sent to dev.ootola.com or anywhere else; the app has no backend to send it to.

Is my database exposed by any of this?

No more than it already is by shipping a client app. The workflow performs a GET on one endpoint you chose, using a key that only has whatever access Row Level Security grants. It can't write, and it can't read anything RLS doesn't already allow the public anon role to read.

Does it matter which table I pick?

No. The workflow only needs to make Postgres do something — any readable table keeps the project awake, regardless of what's in it or how often you use it. This holds even for a table with Row Level Security on and no policy granting the anon role access: PostgREST returns 200 with an empty list rather than an error in that case, because the request still reached the database, which is all that matters here. Only a table that doesn't exist fails, with a 404 — and the setup app won't let you add a project until that exact request has already succeeded once.

A run failed. Do I need to do anything?

Read the first line of the job summary — it answers exactly that before any detail.

Action required means something you own needs changing: a key was rotated, a table was renamed or dropped, a secret is missing, the project was paused or restricted. The section for that project names the specific cause, quotes what Supabase said, and gives the fix.

Outage means Supabase is not answering and nothing in your repository is wrong. There is nothing to do; the next scheduled run will try again. This is the common case, and it is usually the API layer in front of your database rather than the database itself — a 503 comes from Supabase's gateway when PostgREST is unavailable or restarting, so the request never reached Postgres at all.

Runs go red in both cases, since a project you're trying to keep awake being unreachable is worth knowing about either way. The verdict line is what tells them apart.

Can Supalive wake a project that's already paused?

No. A paused project is refused at the gateway, so a ping never reaches Postgres and cannot count as the activity that would have prevented the pause. Supalive keeps an awake project awake; it cannot restore a sleeping one. Restore it yourself from the Supabase dashboard with Resume project.

If a project ever reaches that state, the pings had been failing for a while first. The run history will show when it started.

How do I change the schedule?

Edit the cron expression at the top of .github/workflows/supalive.yml. See installation.md.

Does this cost anything?

A run takes roughly 15 seconds. At twice a day, that's about 2 minutes of GitHub Actions time a month — well inside the free tier for a private repository.

A run where a project is genuinely unreachable takes longer, because retryable failures get three attempts spread across about a minute before the run gives up on that project. That only applies to projects that are actually failing, and only to failures that could plausibly clear on their own.

Will a run happen exactly on schedule?

Not to the second. GitHub's scheduled workflows are best-effort and can run several minutes late during periods of high platform load. That's expected behavior, not a sign something is broken — Supabase's own pause threshold is measured in days, not minutes.

Does GitHub ever disable the schedule on its own?

Only for public repositories: GitHub automatically disables scheduled workflows after 60 days with no repository activity. It emails you before that happens, and re-enabling is one click in the Actions tab. Private repositories are not affected — verified against a private repo with 127 days of zero activity and unbroken scheduled runs throughout. If your Supalive repository is public, this is worth knowing; if it's private, it doesn't apply to you.