Backing up Supabase Storage buckets, the part your database backup skips
User uploads live in Storage, not Postgres, and no Supabase backup plan includes them. How Storage is laid out, how to copy it with rclone or the AWS CLI, and how to keep the copy incremental and encrypted.
Ask most teams "is your Supabase project backed up?" and they'll say yes, meaning the database. Then ask where the profile photos, PDFs and attachments their users uploaded are backed up, and the room goes quiet.
Supabase Storage is an object store. The database holds metadata about each object (the storage.objects table: bucket, path, size, owner), but the bytes live in Supabase's S3-backed Storage service. A pg_dump gets you the list of files and none of the files. Supabase's daily backups and PITR are database-only too.
So a complete backup of a project is two things: the database, and every object in every bucket.
How Storage is laid out
- Buckets, each public or private, with optional size and MIME limits.
- Objects inside buckets, addressed by path (
avatars/user-123/photo.jpg). - Row-level security policies on
storage.objectscontrol who can read and write what through the API.
Storage exposes an S3-compatible endpoint, which is what makes backups practical. In the dashboard: Storage → Settings → S3 connection gives you the endpoint and region, and S3 access keys lets you create key pairs. Create a key pair just for backups so you can revoke it independently.
Copying it with standard tools
rclone is the most comfortable option because it can sync between any two object stores in one command. Configure a remote for Supabase:
# ~/.config/rclone/rclone.conf
[supabase]
type = s3
provider = Other
access_key_id = <your access key>
secret_access_key = <your secret>
endpoint = https://<project-ref>.supabase.co/storage/v1/s3 # copy the exact URL from the dashboard
region = <region shown in the dashboard>
force_path_style = true
Then sync every bucket to another provider (here, a Cloudflare R2 remote you've configured the same way):
for bucket in $(rclone lsd supabase: | awk '{print $NF}'); do
rclone sync "supabase:$bucket" "r2:my-backups/storage/$bucket" \
--checksum --transfers 8 --fast-list
done
rclone sync is incremental: unchanged objects are skipped, so nightly runs move only what changed. With --backup-dir you can also keep deleted and overwritten files instead of mirroring deletions, which is what you want from a backup rather than a mirror.
The AWS CLI works too (aws s3 sync --endpoint-url ...), but it only syncs between S3 and local disk or S3 and S3, and it deletes nothing unless told.
Three things to get right
1. Encrypt before it lands. Supabase encrypts Storage at rest, but your copy is now sitting in another account. rclone crypt wraps a remote so that names and contents are encrypted with a key only you hold. Losing the key loses the backup, so put it somewhere durable.
2. Keep a manifest. Record the object list with sizes and ETags at each run. Without it you can't answer "did the backup on the 12th contain the file that was deleted on the 13th?", and you can't verify a restore.
3. Don't rely on storage.objects from an old database backup to describe a newer file copy. Restore the database and the Storage copy from the same run, or the metadata and the bytes won't line up. Files present in Storage but missing from storage.objects are invisible to the API; rows without files produce 404s.
Restoring
Create the buckets in the target project with the same names and visibility, then rclone copy back into the new project's S3 endpoint. If you restored the database too, the storage.objects rows will already be there and the API will find the files. Check a handful of signed URLs before you announce it's done.
What VaultKeep does with all this
The steps above are what our Storage sync automates: every bucket is inventoried, each object is encrypted individually with a per-backup key before upload, only changed objects transfer on the next run, and a manifest is written alongside the database backup from the same job. Restore recreates the buckets and streams every object back, decrypted, into the project you designate. It's included on paid plans; if you'd rather run rclone yourself, the configuration above is all you need.
Backups you can actually restore
VaultKeep takes encrypted, off-site backups of your Supabase database and Storage, then test-restores them every week and shows you the evidence. Free for one project.
Start backing up free →