July 11, 2026
How to Replace AWS S3 with MinIO in Development (Without Changing Your Code)
Learn how to deploy MinIO with Docker, configure it as an S3-compatible object storage service, connect it to NestJS using the AWS SDK, and…

By Nurul Islam Rimon
3 min read
Learn how to deploy MinIO with Docker, configure it as an S3-compatible object storage service, connect it to NestJS using the AWS SDK, and prepare it for production.
When building modern applications, object storage quickly becomes a necessity. Whether you're storing user avatars, product images, invoices, videos, or backups, you need a reliable storage solution.
For most developers, Amazon S3 is the first choice , and for good reason. It's scalable, reliable, and widely supported.
However, during local development or while working on self-hosted applications, relying on AWS isn't always ideal. You may not want external dependencies, cloud costs, or internet access just to test file uploads.
That's exactly why I started using MinIO.
MinIO provides an S3-compatible object storage server that you can run anywhere , on your laptop, a VPS, Docker, or Kubernetes. Even better, your application code remains almost identical to what you'd write for Amazon S3.
What is MinIO?
MinIO is an open-source, high-performance object storage server that's fully compatible with the Amazon S3 API.
Instead of storing files on your local filesystem, applications store objects inside buckets.
Think of it like this:
Application
│
AWS SDK
│
──────────────
AWS S3
or
MinIOApplication
│
AWS SDK
│
──────────────
AWS S3
or
MinIOThe biggest advantage is simple:
If your application already works with MinIO, switching to Amazon S3 later usually requires changing only the configuration , not your application logic.
Why Choose MinIO?
There are plenty of reasons developers love MinIO:
- Fully S3-compatible
- Open source
- Extremely fast
- Docker-friendly
- Self-hosted
- Production-ready
- Supports presigned URLs
- Versioning
- Replication
- Encryption
- Works with every AWS SDK
Whether you're building a SaaS platform, CMS, eCommerce application, or mobile backend, MinIO fits naturally into your architecture.
Running MinIO with Docker
Let's start by running MinIO using Docker Compose.
services:
minio:
image: minio/minio
container_name: dev_minio
restart: always
environment:
MINIO_ROOT_USER: root
MINIO_ROOT_PASSWORD: adminpass
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "8086:9001"
volumes:
- minio_data:/data
volumes:
minio_data:services:
minio:
image: minio/minio
container_name: dev_minio
restart: always
environment:
MINIO_ROOT_USER: root
MINIO_ROOT_PASSWORD: adminpass
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "8086:9001"
volumes:
- minio_data:/data
volumes:
minio_data:Start it:
docker compose up -ddocker compose up -dOnce it's running:
S3 API
http://localhost:9000http://localhost:9000Web Console
http://localhost:8086http://localhost:8086Login using:
Username: root
Password: adminpassUsername: root
Password: adminpassCreating Your First Bucket
Inside the web console:
- Click Create Bucket
- Enter a bucket name
Example:
uploadsuploadsBuckets are similar to top-level folders.
Inside buckets you'll store objects:
uploads
├── avatars
├── products
├── invoices
└── documentsuploads
├── avatars
├── products
├── invoices
└── documentsAlthough they look like folders, they're actually object prefixes, just like Amazon S3.
Use the official AWS SDK.
npm install @aws-sdk/client-s3npm install @aws-sdk/client-s3Connecting to MinIO
Create an S3 client.
import { S3Client } from "@aws-sdk/client-s3";
export const s3 = new S3Client({
endpoint: "http://localhost:9000",
region: "us-east-1",
forcePathStyle: true,
credentials: {
accessKeyId: "root",
secretAccessKey: "adminpass",
},
});import { S3Client } from "@aws-sdk/client-s3";
export const s3 = new S3Client({
endpoint: "http://localhost:9000",
region: "us-east-1",
forcePathStyle: true,
credentials: {
accessKeyId: "root",
secretAccessKey: "adminpass",
},
});Let's understand these options.
endpoint
Instead of Amazon S3, we're connecting to our local MinIO server.
http://localhost:9000http://localhost:9000region
MinIO ignores regions, but the AWS SDK expects one.
us-east-1us-east-1is commonly used.
forcePathStyle
Amazon S3 supports virtual-hosted URLs like:
https://bucket.s3.amazonaws.com/image.pnghttps://bucket.s3.amazonaws.com/image.pngMinIO generally uses:
http://localhost:9000/bucket/image.pnghttp://localhost:9000/bucket/image.pngSetting
forcePathStyle: trueforcePathStyle: trueensures maximum compatibility.
Uploading Files
Uploading an object is straightforward.
import {
PutObjectCommand,
} from "@aws-sdk/client-s3";
await s3.send(
new PutObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
Body: fileBuffer,
ContentType: "image/png",
}),
);import {
PutObjectCommand,
} from "@aws-sdk/client-s3";
await s3.send(
new PutObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
Body: fileBuffer,
ContentType: "image/png",
}),
);Here:
- Bucket is the storage bucket.
- Key is the object's path.
- Body contains the file data.
- ContentType helps browsers display files correctly.
Downloading Files
Retrieving a file is just as easy.
import {
GetObjectCommand,
} from "@aws-sdk/client-s3";
const object = await s3.send(
new GetObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
);import {
GetObjectCommand,
} from "@aws-sdk/client-s3";
const object = await s3.send(
new GetObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
);The returned object contains a readable stream that can be sent directly to the client.
Deleting Files
import {
DeleteObjectCommand,
} from "@aws-sdk/client-s3";
await s3.send(
new DeleteObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
);import {
DeleteObjectCommand,
} from "@aws-sdk/client-s3";
await s3.send(
new DeleteObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
);Generating Presigned URLs
One of the best features of S3-compatible storage is presigned URLs.
Instead of uploading files through your backend:
Browser
│
Backend
│
MinIOBrowser
│
Backend
│
MinIOYou can upload directly:
Browser
│
▼
MinIOBrowser
│
▼
MinIOThis reduces server load and improves upload performance.
Generate an upload URL:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { PutObjectCommand } from "@aws-sdk/client-s3";
const url = await getSignedUrl(
s3,
new PutObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
{
expiresIn: 300,
},
);import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { PutObjectCommand } from "@aws-sdk/client-s3";
const url = await getSignedUrl(
s3,
new PutObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
{
expiresIn: 300,
},
);For downloads:
const url = await getSignedUrl(
s3,
new GetObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
{
expiresIn: 300,
},
);const url = await getSignedUrl(
s3,
new GetObjectCommand({
Bucket: "uploads",
Key: "avatars/user-1.png",
}),
{
expiresIn: 300,
},
);Using the MinIO Client (mc)
Although the web console is great for browsing objects, the MinIO Client (mc) is the preferred way to manage servers.
Configure your server:
mc alias set local http://localhost:9000 root adminpassmc alias set local http://localhost:9000 root adminpassList buckets:
mc ls localmc ls localList objects:
mc ls local/uploadsmc ls local/uploadsUpload a file:
mc cp image.png local/uploadsmc cp image.png local/uploadsDownload a file:
mc cp local/uploads/image.png .mc cp local/uploads/image.png .Generate a temporary download link:
mc share download local/uploads/image.pngmc share download local/uploads/image.pngMake a bucket publicly readable:
mc anonymous set download local/uploadsmc anonymous set download local/uploadsThe CLI becomes especially useful when managing production servers or automating deployments.
My Package for object management: https://www.npmjs.com/package/nestjs-r2-storage
You can use it for complex object based file handling.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Code. Scale. Repeat.
— Nurul Islam Rimon,
Fullstack Dev at ExpertSquad.net