Storage Buckets & Nightly Backups

Where files live

Uploaded files — gym media, contract assets, member progress photos, maintenance photos and videos, SAR packages and the rest — are held in storage buckets rather than in the database. Buckets are either public (served straight to a website) or private (reached only through a short-lived signed link).

Nightly backup to Cloudflare R2

The GitHub workflow .github/workflows/backup-storage.yml runs daily at 03:00 UTC and mirrors the configured buckets into Cloudflare R2. Fifteen buckets are covered, including the private sar-packages bucket.

It is a mirror, not an archive: when a file is deleted at source — such as a SAR pack purged after thirty days — the deletion carries across on the next run. That is intentional for anything with a retention rule, and worth remembering before you rely on R2 to recover something a gym deleted a week ago.

Adding a new bucket

  1. Create the bucket in every environment. A bucket that exists in development but not production produces a 404 "Bucket not found" the first time the feature is used for real.
  2. Set it private unless the files are genuinely meant to be public.
  3. Add it to the bucket list in the backup workflow, otherwise it is silently unprotected.
  4. Where a one-off creation script exists in docs/, use it — the scripts are written to be safe to re-run.

Common symptoms

SymptomUsual cause
"Bucket not found" on an action that works in developmentThe bucket was never created in that environment
Download link works then stopsSigned links are deliberately short-lived; generate a fresh one
Upload rejectedPer-bucket size limit or an unsupported file type