Run

The same file API in development and in production.

A local disk in development and S3 in production are the same interface, so the upload path you tested is the upload path that ships. Swapping the driver is a config change, not a rewrite of every call site.

What you get.

All of it in the same install, with nothing to wire together first.

Drivers

Local disk and S3 behind one API, chosen per disk in config so an app can use both at once.

Uploads

Validated multipart handling that hands back a stored path, not a temp file you have to remember to move.

Signed URLs

Time-limited URLs for private objects, so a download link can expire without a proxy route in front of it.

Visibility

Public and private per file, enforced by the driver rather than by whichever route happened to serve it.

File utilities

Path helpers, mime detection, streaming reads, and directory listings that behave the same on every driver.

CDN

Production buckets sit behind the CDN that buddy deploy provisions, from the same TypeScript config.

From the command line.

Buddy scaffolds, runs, and ships this the same way it does the rest of the app.

buddy make:action UploadAvatar buddy make:model Attachment

These share the same models, config, and types, so using them together costs nothing extra.