Supabase pauses free tier projects after one week of no database activity. For a portfolio that does not receive constant traffic, this is a real concern. This article covers how I set up a scheduled heartbeat to keep it alive, using Render's cron job service and Sentry's Cron Monitors.
Supabase's free tier is generous, but it has one condition: if no database queries are made for seven days, the project is paused. Resuming it requires logging into the dashboard and clicking a button. For a public portfolio, that is not acceptable.
The solution is simple in principle: make at least one database query per week. The question is how.
UptimeRobot or cron-job.org — external services that ping a URL on a schedule. This works, and has a useful side effect: it also keeps Render's free web service awake, since Render spins down services after 15 minutes of inactivity. The downside is that an HTTP ping only tells you the app responded. It does not tell you whether a scheduled task ran, or whether the database query actually succeeded.
Render Cron Job + Sentry Cron Monitors — a scheduled job running inside the app's own environment, querying the database directly, and reporting its status to Sentry. The advantage: Sentry alerts you not just when the app is down, but when the scheduled task is missed entirely or throws an error. I was already using Sentry for error tracking, so this was the natural choice.
In the Sentry dashboard, go to Crons → Create Monitor. The important fields:
portfolio-supabase-heartbeat.I created a management command at core/management/commands/heartbeat.py. It does the minimum needed: one lightweight database query, wrapped in a Sentry monitor context manager.
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from sentry_sdk.crons import monitor
class Command(BaseCommand):
help = "Keeps Supabase active and reports status to Sentry Cron Monitor."
def handle(self, *args, **options):
with monitor(monitor_slug="portfolio-supabase-heartbeat"):
get_user_model().objects.exists()
self.stdout.write(self.style.SUCCESS("Heartbeat OK"))
The monitor context manager from sentry_sdk.crons handles all check-in logic automatically: IN_PROGRESS on entry, OK on clean exit, ERROR if an exception is raised. get_user_model().objects.exists() is the lightest possible database query — it checks for any user row without loading data.
My first version called sentry_sdk.capture_checkin() directly. This does not exist at the module level in sentry-sdk 2.x — the check-in API lives in sentry_sdk.crons, not in sentry_sdk. The fix was to use the monitor context manager instead, which is simpler anyway since it removes the need to track check-in IDs manually.
In Render I created a new Cron Job using the existing Docker image — the same image the web service uses, so no extra build step is needed. The start command is python manage.py heartbeat and the schedule matches the Sentry monitor exactly.
Render Cron Jobs do not automatically share environment variables with the web service. The solution is Environment Groups: define the variables once in a named group, then link that group to both services. The heartbeat only needs a subset of the full environment:
SECRET_KEYDEBUG=FalseDATABASE_URLSENTRY_DSNNo S3 storage variables are needed — the heartbeat command does not touch files.
With the cron job running daily, Supabase sees regular database activity and stays awake. Sentry confirms every morning that the job ran — or alerts me immediately if it did not.