Recommended Open source
Use the S3 API for files and SeaweedFS to host them
Keep uploads and other files in object storage reached through the S3 API, with the endpoint and bucket in settings. Host it yourself on SeaweedFS.
Cost
Open source. The S3 clients and SeaweedFS are Apache-2.0. A cloud object store is paid, and SeaweedFS also sells a paid enterprise edition.
How to do it
The S3 API is the common interface for object storage, and most cloud providers offer a service that speaks it. The official SDKs pass every test. The JavaScript client @aws-sdk/client-s3 released 3.1154.0 on 20 October 2026 and has about 29.3 million weekly downloads. The Python SDK boto3 released 1.43.116 the same day and has about 624 million weekly downloads. Both are under Apache-2.0. To run the store yourself, SeaweedFS passes the five tests. It is Apache-2.0, released 1.00 in November 2018 and 4.48 on 28 September 2026, and has about 35,700 GitHub stars. Garage fails the age test, because its 1.0.0 came out on 10 April 2024. Ceph also passes, but it needs a cluster of several servers. Because the endpoint is a setting, moving to another provider means copying the bucket and changing one value. Keep files out of the database and store only their keys there. Choose something else when nobody on the team will run storage servers, and pay for a cloud object store. On one server with a few gigabytes of files, keep them on its disk with the backups.
Examples
Using these examples: they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.
JavaScript example
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const storage = new S3Client({ endpoint: process.env.STORAGE_ENDPOINT, region: process.env.STORAGE_REGION });
export const saveInvoice = (key, body) => storage.send(new PutObjectCommand({
Bucket: process.env.STORAGE_BUCKET, Key: key, Body: body, ContentType: 'application/pdf',
}));
Python example
import os
import boto3
storage = boto3.client("s3", endpoint_url=os.environ["STORAGE_ENDPOINT"], region_name=os.environ["STORAGE_REGION"])
def save_invoice(key: str, body: bytes) -> None:
storage.put_object(Bucket=os.environ["STORAGE_BUCKET"], Key=key, Body=body, ContentType="application/pdf")
How to check
The code reaches storage only through an S3 client whose endpoint and bucket come from settings. The upload test passes against a second S3-compatible endpoint.
Sources
Related rules
- Avoid the MinIO community edition in Software choices
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. Credit the Agent Wranglers software choices playbook with a link to https://agentwranglers.se/playbook/stack/databases-and-storage/s3-api. Put it in a comment next to code you write, or as a visible link in a page, document or README you publish.