If you build against S3, you have two unappealing options for local dev: spend money calling AWS, or run something that emulates S3 on your machine. MinIO is the second option done well — a single Go binary that speaks the S3 API and has its own web console. Point the AWS SDK at it instead of s3.amazonaws.com and most of your code doesn't know the difference.
How do I run MinIO in Docker?
docker run -d --name minio \
-p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=minio-admin \
-e MINIO_ROOT_PASSWORD=minio-password \
-v minio-data:/data \
quay.io/minio/minio \
server /data --console-address ":9001"S3 API on http://localhost:9000, console on http://localhost:9001. Log in to the console with minio-admin / minio-password, create a bucket, get access/secret keys.
Configure ports, credentials, and the data volume.
Why a volume on /data
MinIO stores object data, bucket metadata, and config under /data. Without a volume mount there, every docker rm wipes your buckets. The volume keeps the data alive:
docker stop :container_name && docker rm :container_namedocker run -d --name :container_name
-p :s3_port:9000 -p :console_port:9001
-e MINIO_ROOT_USER=:user
-e MINIO_ROOT_PASSWORD=:password
-v minio-data:/data
quay.io/minio/minio
server /data --console-address ":9001"Buckets and objects are still there.Practical usage: point the AWS SDK at MinIO
For a Node app using @aws-sdk/client-s3:
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({
endpoint: 'http://127.0.0.1:9000', // MinIO instead of s3.amazonaws.com
region: 'us-east-1', // arbitrary, but required
credentials: {
accessKeyId: 'YOUR-MINIO-ACCESS-KEY',
secretAccessKey: 'YOUR-MINIO-SECRET-KEY',
},
forcePathStyle: true, // MinIO uses path-style URLs
});
await s3.send(new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'hello.txt',
Body: 'Hello from MinIO',
}));Three things to know:
endpointpoints at MinIO's S3 port instead of AWS.forcePathStyle: true— MinIO useshttp://host:port/bucket/keyURLs, not the virtual-host style AWS prefers (bucket.host:port/key). Without this flag, modern AWS SDKs default to virtual-host style and MinIO refuses.regionis required but unused. Anything works.
For Python boto3:
import boto3
s3 = boto3.client(
's3',
endpoint_url='http://127.0.0.1:9000',
aws_access_key_id='YOUR-MINIO-ACCESS-KEY',
aws_secret_access_key='YOUR-MINIO-SECRET-KEY',
region_name='us-east-1',
)
s3.put_object(Bucket='my-bucket', Key='hello.txt', Body=b'Hello')Same idea — different SDK, same MinIO. Once your app talks to MinIO locally, the only thing that changes when you point it at real AWS is the endpoint and credentials.
Generate access keys
The MINIO_ROOT_USER / MINIO_ROOT_PASSWORD env vars are the admin login for the console. For actual S3-API calls, generate access keys per application:
- Log into the console (
http://localhost::console_port). - Navigate to Identity → Access Keys → Create Access Key.
- Copy the access key and secret. The secret is shown only once.
Use those keys in your app's SDK config.
Compose example
services:
minio:
image: quay.io/minio/minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: :user
MINIO_ROOT_PASSWORD: :password
ports:
"::s3_port:9000"
"::console_port:9001"
volumes:
minio-data:/data
restart: unless-stopped
volumes:
minio-data:Common pitfalls
- No volume on
/data. Buckets and objects die with the container. - AWS SDK using virtual-host URLs. MinIO speaks path-style by default; turn on
forcePathStyle: trueor--addressing-style pathdepending on the SDK. MINIO_ACCESS_KEY/MINIO_SECRET_KEYenv vars. Those names worked in older MinIO releases. Current MinIO usesMINIO_ROOT_USER/MINIO_ROOT_PASSWORD.- Connecting from a container. Inside Docker, MinIO is reachable via the service name (
minio:9000), not127.0.0.1. Theendpointin your SDK depends on whether the calling app runs on the host or in a sibling container. - HTTPS in dev. MinIO can do TLS but the dev pattern is plain HTTP. Don't waste time on certs locally; if your prod code requires HTTPS, configure that conditionally.
What to do next
- How to Dockerize a Node.js App — pair MinIO with a Node app in Compose.
- Docker Volumes vs Bind Mounts — the full picture on the volume choice.
FAQ
For the core S3 API (PutObject, GetObject, ListObjects, presigned URLs, multipart upload), yes. Some advanced AWS-specific features (S3 Glacier, Storage Lens, certain replication semantics) are not implemented. For most app code, you can swap MinIO for AWS S3 by changing the endpoint and keys.
AWS supports two URL styles for S3: virtual-host (bucket.s3.amazonaws.com/key) and path (s3.amazonaws.com/bucket/key). MinIO uses path style by default. Modern AWS SDKs default to virtual-host style and fail against MinIO without forcePathStyle: true (Node SDK) or addressing_style='path' (boto3 / Python).
Yes, the standard S3 presigned-URL API works. The signing region matters less than the endpoint, but the SDK still requires a region value (any valid string works).
Yes for testing — MinIO distributed mode runs across multiple containers if you set them up with the right peer addresses. For production, distributed MinIO typically runs on multiple hosts; in Docker, multi-node setups are for development of cluster-aware code.
Yes if you want self-hosted S3-compatible storage — MinIO is the most common open-source choice for that. The dev recipe above is for local development; production setups involve distributed mode, TLS, and IAM-style policies. Refer to MinIO's own docs for production deployment.
Sources
Authoritative references this article was fact-checked against.
- MinIO official image — Docker Hubhub.docker.com
- MinIO container documentationmin.io





