Storage (S3)
Use a project's S3 bucket with any S3 client; serve public files from the public/ prefix.
Every project has a bucket on Garage, an S3-compatible store. mybase project create prints the endpoint, region (garage), bucket, access key id, and secret key (once).
Use it from an app
Any S3 client works. Use path-style addressing:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION, // "garage"
forcePathStyle: true,
credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID!, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY! },
});
await s3.send(new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: "public/logo.png", Body: bytes }));Bun's built-in S3Client and aws4fetch work too.
Public files
Objects whose key starts with public/ are readable by anyone at:
https://mybase.example.com/storage/<project>/public/<rest of key>Everything else needs the S3 credentials (or a presigned URL from your app).
Public files are served from the same origin as the dashboard, so they're sandboxed: every
response has X-Content-Type-Options: nosniff and Content-Security-Policy: … sandbox. Types a
browser could run or render as a page (HTML, SVG, XML, JavaScript, WebAssembly, or no type at all)
are sent as application/octet-stream downloads instead. Images, video, audio, fonts, PDFs, CSS,
JSON, and text are shown inline as usual. To show an SVG, serve it from your app or convert it to PNG.
Browse files
Dashboard → Storage, or GET /projects/<p>/files?prefix=<folder/> with storage:read (MCP list_files).
Security
- Everything under
public/is readable by anyone. Keep private files elsewhere and serve them through your app. - Keep S3 keys server-side; use presigned URLs for browser uploads. Over the internet, use the tunnel's HTTPS endpoint, not raw :3900.
More: Security best practices.
Agent notes
- The bucket name is the project name with
_replaced by-. - Set
forcePathStyle: true(AWS SDK) or the client tries<bucket>.<host>and fails. - Through a Cloudflare Tunnel, single requests over 100 MB fail; use multipart uploads.
- HTML/SVG/JS under
public/download instead of rendering (security). Don't host a website from the bucket.