Free automated PostgreSQL database backups from Supabase to Cloudflare R2 storage, powered by GitHub Actions. Zero infrastructure, zero cost. Set up in 5 minutes.
Status: Scheduled backups are DISABLED. The workflow requires repository secrets before it can run. See Quick Start to configure them. Manual runs from the Actions tab will fail until secrets are added.
Supabase's free tier doesn't include automated backups. If your database gets corrupted or you accidentally drop a table, you're out of luck. This workflow dumps your entire database (roles, schema, and data) to Cloudflare R2 every day, giving you point-in-time recovery without paying for Supabase's backup add-on.
Cloudflare R2 has no egress fees, and GitHub Actions gives you 2,000 free minutes per month on private repos (unlimited on public). The math works out to completely free daily backups for most projects.
- Dumps roles, schema, and data as separate SQL files
- Compresses everything into a dated zip archive
- Uploads directly to Cloudflare R2 with the AWS S3 API
- Optional 14-day auto-delete lifecycle policy on R2 (keeps storage cheap)
- Manual trigger or cron schedule
- Runs on GitHub's free tier runners (no self-hosting needed)
Supabase DB ──▶ GitHub Actions runner ──▶ Cloudflare R2 bucket
(pg_dump via (backup-YYYY-MM-DD.zip)
Supabase CLI)
Every run creates a zip file containing three SQL dumps:
roles.sql— Database roles and permissionsschema.sql— Full table structure, indexes, and functionsdata.sql— All row data (uses COPY for speed)
Files are named backup-YYYY-MM-DD.zip and uploaded to your R2 bucket.
git clone https://github.com/cucoleadan/supabase-to-r2-backup.gitGo to Cloudflare Dashboard → R2 → Create bucket. Name it something like supabase-backups.
Optional but recommended: set a lifecycle rule to auto-delete backups older than 14 days. Go to bucket Settings → Object lifecycle → Create rule with action Delete, prefix backup-, and 14 days.
In the R2 dashboard, go to Manage R2 API Tokens → Create API token. Set permissions to Object Read & Write, scope it to your backup bucket. Save the Access Key ID and Secret Access Key immediately.
In your Supabase project: Settings → Database → Connection info. Select Session pooler (port 5432) and copy the full PostgreSQL URI. Do not use the direct connection, it resolves to IPv6 and breaks on GitHub Actions runners.
Go to your repo's Settings → Secrets and variables → Actions. Add these five secrets:
| Secret | Value |
|---|---|
SUPABASE_DB_URL |
Session pooler connection string (e.g. postgresql://postgres.xxx:password@aws-0-eu-central-1.pooler.supabase.com:5432/postgres) |
CF_ACCESS_KEY_ID |
Cloudflare R2 Access Key ID |
CF_SECRET_ACCESS_KEY |
Cloudflare R2 Secret Access Key |
CF_BUCKET_NAME |
Your R2 bucket name |
CF_BUCKET_ENDPOINT |
R2 S3 endpoint (e.g. https://<account-id>.r2.cloudflarestorage.com) |
Uncomment the schedule block in .github/workflows/backup.yml:
on:
workflow_dispatch:
schedule:
- cron: '0 2 * * *' # Runs daily at 2 AM UTCThe workflow also supports manual runs from the Actions tab for testing.
Download the zip from your R2 bucket, extract it, and restore:
# Restore roles first
psql "$SUPABASE_DB_URL" -f roles.sql
# Then schema
psql "$SUPABASE_DB_URL" -f schema.sql
# Then data
psql "$SUPABASE_DB_URL" -f data.sqlOr use the Supabase CLI:
supabase db restore --db-url "$SUPABASE_DB_URL" backup-2026-05-11.zipYour connection string is using the direct connection. Switch to the session pooler URL (port 5432) in Supabase settings.
Reset your database password in Supabase Settings → Database, then update the SUPABASE_DB_URL secret with the new connection string.
The workflow secrets are missing or empty. Verify all five secrets are set in your repository settings.
The workflow uses the official Supabase CLI for dumps. If you get CLI errors, make sure your SUPABASE_DB_URL uses the session pooler and includes the full connection string with password.
- GitHub Actions: 2,000 free minutes/month (private repos) or unlimited (public). Daily backups use ~2 minutes each, well under the limit.
- Cloudflare R2: 10 GB free storage. A typical Supabase backup is 1-50 MB. With a 14-day lifecycle, you'll store 14-28 files max. Egress is free.
- Total: $0 for most projects.
MIT