DEV Community

Alex Spinov
Alex Spinov

Posted on

MinIO Has a Free API — S3-Compatible Object Storage You Can Self-Host

MinIO is the S3-compatible object storage you can run locally or on your own servers — same API as AWS S3, but free and open source. Used by companies handling petabytes of data.

Why MinIO?

  • S3-compatible — use AWS SDK, works with any S3-compatible tool
  • Self-hosted — your data, your servers, no cloud bills
  • Fast — 325 GiB/s throughput on standard hardware
  • Kubernetes-native — built for cloud-native deployments
  • Erasure coding — data protection without RAID
  • Versioning, lifecycle, encryption — enterprise features, free

Quick Start (Docker)

docker run -d \
  --name minio \
  -p 9000:9000 \
  -p 9001:9001 \
  -v minio-data:/data \
  -e MINIO_ROOT_USER=minioadmin \
  -e MINIO_ROOT_PASSWORD=minioadmin \
  minio/minio server /data --console-address ":9001"

# Console UI: http://localhost:9001
# API: http://localhost:9000
Enter fullscreen mode Exit fullscreen mode

Node.js Client

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Enter fullscreen mode Exit fullscreen mode
import {
  S3Client,
  PutObjectCommand,
  GetObjectCommand,
  ListObjectsV2Command,
  DeleteObjectCommand,
} from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "http://localhost:9000",
  region: "us-east-1",
  credentials: {
    accessKeyId: "minioadmin",
    secretAccessKey: "minioadmin",
  },
  forcePathStyle: true,
});

// Upload a file
await s3.send(new PutObjectCommand({
  Bucket: "my-bucket",
  Key: "images/photo.jpg",
  Body: Buffer.from("file contents"),
  ContentType: "image/jpeg",
}));

// Download a file
const { Body } = await s3.send(new GetObjectCommand({
  Bucket: "my-bucket",
  Key: "images/photo.jpg",
}));
const content = await Body.transformToString();

// List files
const { Contents } = await s3.send(new ListObjectsV2Command({
  Bucket: "my-bucket",
  Prefix: "images/",
}));
console.log(Contents.map(obj => obj.Key));

// Delete
await s3.send(new DeleteObjectCommand({
  Bucket: "my-bucket",
  Key: "images/photo.jpg",
}));
Enter fullscreen mode Exit fullscreen mode

Presigned URLs (Direct Upload from Browser)

import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

// Generate upload URL (valid for 1 hour)
const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({
  Bucket: "uploads",
  Key: `avatars/${userId}.jpg`,
  ContentType: "image/jpeg",
}), { expiresIn: 3600 });

// Frontend can upload directly to MinIO:
// fetch(uploadUrl, { method: 'PUT', body: file })

// Generate download URL
const downloadUrl = await getSignedUrl(s3, new GetObjectCommand({
  Bucket: "uploads",
  Key: `avatars/${userId}.jpg`,
}), { expiresIn: 3600 });
Enter fullscreen mode Exit fullscreen mode

Express.js File Upload API

import express from "express";
import multer from "multer";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

const s3 = new S3Client({
  endpoint: "http://localhost:9000",
  region: "us-east-1",
  credentials: { accessKeyId: "minioadmin", secretAccessKey: "minioadmin" },
  forcePathStyle: true,
});

app.post("/upload", upload.single("file"), async (req, res) => {
  const key = `uploads/${Date.now()}-${req.file.originalname}`;

  await s3.send(new PutObjectCommand({
    Bucket: "my-app",
    Key: key,
    Body: req.file.buffer,
    ContentType: req.file.mimetype,
  }));

  res.json({
    url: `http://localhost:9000/my-app/${key}`,
    key,
    size: req.file.size,
  });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Bucket Policies (Public Read)

# Using MinIO client (mc)
mc alias set local http://localhost:9000 minioadmin minioadmin

# Create bucket
mc mb local/public-assets

# Make bucket publicly readable
mc anonymous set download local/public-assets

# Upload public file
mc cp logo.png local/public-assets/
# Now accessible at: http://localhost:9000/public-assets/logo.png
Enter fullscreen mode Exit fullscreen mode

Docker Compose (Dev Environment)

services:
  app:
    build: .
    environment:
      S3_ENDPOINT: http://minio:9000
      S3_ACCESS_KEY: minioadmin
      S3_SECRET_KEY: minioadmin
      S3_BUCKET: my-app
    depends_on:
      - minio

  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    volumes:
      - minio-data:/data

  # Auto-create bucket on startup
  minio-init:
    image: minio/mc
    depends_on:
      - minio
    entrypoint: >
      /bin/sh -c "
      mc alias set local http://minio:9000 minioadmin minioadmin;
      mc mb local/my-app --ignore-existing;
      mc anonymous set download local/my-app;
      "

volumes:
  minio-data:
Enter fullscreen mode Exit fullscreen mode

MinIO vs AWS S3 vs Google Cloud Storage

Feature MinIO AWS S3 GCS
Cost Free (self-hosted) Pay per GB/request Pay per GB/request
S3 compatible Yes (native) Native Via interop
Self-hosted Yes No No
Latency Local (sub-ms) ~50-100ms ~50-100ms
Max object 5TB 5TB 5TB
Encryption Yes Yes Yes
Versioning Yes Yes Yes
Replication Built-in Cross-region Cross-region

Need to scrape data from any website and get it in structured JSON? Check out my web scraping tools on Apify — no coding required, results in minutes.

Have a custom data extraction project? Email me at spinov001@gmail.com — I build tailored scraping solutions for businesses.

Top comments (0)