Uploading Objects
s3BEAR chooses one of two upload paths automatically, based on file size, so small files stay simple and large files never bottleneck on the backend.
Two upload paths
- < 100 MB → simple upload. The file is POSTed to the backend as
multipart/form-data; the backend writes it to S3. - ≥ 100 MB → multipart upload via presigned URLs. The file is split into
10 MB parts (configurable). The backend issues a presigned
PUTURL for each part; the browser uploads the parts directly to S3-compatible storage, bypassing the backend, and the backend then finalizes the upload. This eliminates double bandwidth costs and sidesteps the FastAPI request-size limit.
The multipart flow
POST /api/v1/buckets/{name}/upload/init— the backend creates the multipart upload and returns N presigned URLs.- The browser uploads each part with
PUTdirectly to S3, capturing theETagfrom every response. POST /api/v1/buckets/{name}/upload/complete— the backend finalizes the object with the ordered list of ETags. (POST /upload/abortcancels an in-progress upload.)
Direct browser-to-storage transfer requires CORS on the storage layer, including an exposed
ETagheader. See CORS Setup.
How to use
In the UI, drag & drop into the bucket browser — the frontend routes to the correct path for you.
Programmatic simple upload:
curl -X POST "http://localhost:8200/api/v1/buckets/my-bucket/objects?prefix=2026/" \
-H "Authorization: Bearer $TOKEN" \
-F "[email protected]"
Configuration
| Env variable | Default | Purpose |
|---|---|---|
PRESIGNED_URL_BASE | "" | External URL the browser uses to reach S3-compatible storage (URLs are signed against this host). Required when the backend and browser see storage at different hostnames. |
MULTIPART_PART_SIZE_MB | 10 | Part size for multipart uploads |
PRESIGNED_URL_EXPIRY_SECONDS | 3600 | Presigned URL lifetime |
Why
PRESIGNED_URL_BASEmatters: in Kubernetes the backend reaches MinIO viahttp://minio.minio-ns.svc:9000but the browser hits it viahttps://minio.example.com. Presigned URLs are bound to the host they were signed against — if the host doesn't match, the storage service returns 403 SignatureDoesNotMatch. See Troubleshooting.
Use case: uploading a 4 GB dataset
A data scientist uploads a 4 GB Parquet file from their browser. The frontend calls
/upload/init, gets back 410 presigned URLs (4 GB ÷ 10 MB), then uploads all
410 parts to MinIO in parallel (browsers handle ~6 concurrent connections per host). The
backend never touches the file payload — it only signs URLs and finalizes the manifest.
The upload finishes in minutes instead of hours, and the FastAPI worker isn't tied up
streaming gigabytes.
Related: CORS Setup · Configuration · Troubleshooting
