Documentation menu

Upload files to MinIO

MinIO is self-hosted S3-compatible storage, which makes it the fastest way to develop against real object storage without a cloud account — upup's own end-to-end suite uploads to a local MinIO container. Everything you configure here transfers to a hosted provider later: only endpoint, region, and the credentials change.

This page covers server mode, where @upupjs/server holds the credentials. See Client Mode vs Server Mode if you would rather sign URLs yourself and have the browser upload directly.

The config

Only the storage block differs from the AWS S3 example — keep uploadTokenSecret and the rest of the handler as they are there.

ts
const storage = {
    type: 'minio',
    bucket: process.env.MINIO_BUCKET!,
    region: 'us-east-1', // MinIO's default region
    endpoint: 'http://localhost:9100', // your MinIO S3 endpoint
    forcePathStyle: true, // required by MinIO
    accessKeyId: process.env.MINIO_ACCESS_KEY,
    secretAccessKey: process.env.MINIO_SECRET_KEY,
}

MinIO requires path-style addressing — requests go to endpoint/bucket/key, not bucket.endpoint/key. upup already defaults forcePathStyle to true whenever endpoint is set, so the line above is explicit rather than necessary. region is us-east-1 unless you deliberately configured MinIO otherwise; it still has to match, because the signature covers it.

Running MinIO locally

bash
docker run -p 9100:9000 -p 9101:9001 \
  -e MINIO_ROOT_USER=upupadmin \
  -e MINIO_ROOT_PASSWORD=upupadmin123 \
  -e MINIO_API_CORS_ALLOW_ORIGIN='*' \
  -v minio-data:/data \
  minio/minio server /data --console-address ':9001'

MinIO listens on two ports and they are not interchangeable. The S3 API is on container port 9000 (published as 9100 above) and is what endpoint must point at; the web console is on 9001 (published as 9101) and is for humans. Aiming endpoint at the console port produces confusing HTML-instead-of-XML errors.

Pick published ports deliberately. 9000 and 9001 are MinIO's defaults, so a container from another project may already own them — publishing on 9100/9101, as above, avoids uploading into somebody else's bucket.

Create the bucket either from the console at http://localhost:9101 or with the MinIO client:

bash
mc alias set local http://localhost:9100 upupadmin upupadmin123
mc mb local/upup-uploads

The endpoint must be reachable by whoever uploads

In client mode the presigned URL your server signs is opened by the browser, so endpoint has to be a host the browser can resolve. http://localhost:9100 works on your laptop and fails from any other device, from a container on a different network, and from a phone on the same Wi-Fi. Use the machine's LAN address or a hostname both sides agree on. In server mode only your server connects to MinIO, so an internal Docker network name such as http://minio:9000 is fine.

CORS (client mode only)

MinIO takes its CORS policy from the server process, not from per-bucket rules: set MINIO_API_CORS_ALLOW_ORIGIN to a comma-separated list of origins (* is fine locally, as in the container above; name your real origins in production). In server mode the browser only talks to your server, so CORS never enters the upload path. See Credentials And CORS.

Going to production

The root user is the equivalent of an account owner. For anything beyond local development, create a dedicated MinIO user with a policy limited to the upload bucket and use that key pair, terminate the endpoint over HTTPS, and keep the console port off the public internet.

Because MinIO is the S3 API, moving to a hosted provider later is a change of three config values and nothing else — see Other S3-compatible services.

Next steps