Storage
The template includes an S3-compatible object storage client. Locally it talks to RUSTFS (or MinIO-compatible) from Docker Compose. On deploy, Fly uses Tigris and Railway uses an S3-compatible bucket. The app code stays the same: change the endpoint and keys, not the upload calls.
Client
Initialized at backend startup from apps/backend/src/libs/storage/storage.ts:
s3 = new S3Client({
endpoint: process.env.RUSTFS_ENDPOINT,
region: "us-east-1",
credentials: {
accessKeyId: process.env.RUSTFS_ACCESS_KEY,
secretAccessKey: process.env.RUSTFS_SECRET_KEY,
},
forcePathStyle: true,
});Helpers on that module cover upload, download, signed URLs, and delete. The client auto-creates the bucket on first upload when the provider allows it.
Environment variables
| Variable | Purpose |
|---|---|
RUSTFS_ENDPOINT | S3 API endpoint (local Docker URL or cloud endpoint) |
RUSTFS_ACCESS_KEY | Access key |
RUSTFS_SECRET_KEY | Secret key |
BUCKET_NAME | Default bucket for app uploads |
deploy wires these from the provider you pick for --bucket. For a database or bucket you already
run, use the external block in stack.json (same pattern as bring-your-own Postgres).
Upload middleware
HTTP uploads go through apps/backend/src/middleware/file/:
import { fileUpload, memoryStorage } from "@backend/middleware/file";
app.post(
"/avatar",
fileUpload({
storage: memoryStorage(),
limits: { fileSize: 5 * 1024 * 1024, files: 1 },
}).single("file"),
async (c) => {
const file = c.get("file");
// uploadFile({ bucket, key, body: file.buffer, contentType: file.mimetype })
return c.json({ ok: true });
},
);Prefer diskStorage for larger files. Always set limits.fileSize and a fileFilter when only images (or another MIME
set) are allowed.
What packs use it for
After a starter or feature pack install, storage typically backs:
- User avatars
- Organization logos
- Team / project icons
- Board or media images in collaboration packs
Those features import @backend/libs/storage/storage and the file middleware. They do not bring their own storage
provider.
Local vs production
| Environment | Typical backend |
|---|---|
| Local Docker | RUSTFS / MinIO-compatible service in docker-compose.yml |
| Fly.io deploy | Tigris (S3 API) |
| Railway deploy | Railway's S3-compatible bucket / RustFS service |
Keep forcePathStyle: true unless your provider documents otherwise. Path-style addressing is what the local and
deployed setups expect.
Related
Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!
