Storage
AWS S3, Cloudflare R2, Backblaze B2, Tigris, MinIO, Garage and SeaweedFS all
work. Point the S3_* variables at one:
S3_ENDPOINT=https://s3.eu-central-1.amazonaws.comS3_REGION=eu-central-1S3_FORCE_PATH_STYLE=falseS3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.comS3_REGION=autoS3_FORCE_PATH_STYLE=trueWhat the server sets up
Section titled “What the server sets up”On start, the server creates the bucket when it is missing, allows browser
uploads from APP_URL (a CORS rule), and, when the bucket has no lifecycle
rules yet, adds two: staging/ expires after a day, renditions/ after 30. A
key that can’t change bucket settings (R2’s object tokens can’t) logs a
warning instead: set those three in the provider’s console.
What is in the bucket
Section titled “What is in the bucket”| Prefix | |
|---|---|
assets/{sha256} |
Originals, by the hash of their bytes. Never overwritten; shared by every project holding the same bytes |
previews/{sha256} |
Stills for files that aren’t images (PDF, PSD, video) |
renditions/ |
Sizes and formats made on request. Expire after 30 days and come back on the next request |
staging/{project}/ |
Browser uploads before they are filed. Expire after a day |
sweep-owners/{id} |
One per database that sweeps the bucket, holding its APP_URL |
Back up assets/ and previews/. Renditions and staged uploads are
disposable.
When bytes leave
Section titled “When bytes leave”Deleting an asset keeps it, restorable, for 30 days. Then the sweeper, which runs in the app every six hours, removes the row, and the original once no asset in any project holds those bytes. Deleting a project or an organization removes its rows at once, and its files at the next sweep. A file written in the last day is never swept, so an upload in flight is safe.
One bucket per database
Section titled “One bucket per database”The sweeper removes every original its own database doesn’t hold, so two databases on one bucket (a staging copy, a benchmark, a second install) would remove each other’s files. Give each database its own bucket.
To catch the mistake, each sweep leaves a marker, sweep-owners/{id}, with the
id of its database, and deletes nothing while the bucket holds another
database’s marker. The server logs a warning naming the markers instead. When
a database that shared the bucket is gone for good, delete its marker (it
holds the APP_URL of the install that wrote it) and sweeping resumes at the
next run.