Object Storage Infrastructure

Build Your Own S3-Compatible Storage and Save 90% on Costs

Deploy MinIO on your servers for AWS S3-compatible object storage at a fraction of cloud provider prices.

March 2026

The storage bill that forced me to find an alternative

Our startup was storing user uploads, backups, and application assets on AWS S3. Thought it was the "standard" choice. Monthly bill started at $500, then $1,200, then hit $3,800. And that was just storage - egress fees doubled it.

I looked at the breakdown carefully. We had 50TB of data. AWS S3 Standard storage: $1,150/month. S3 Glacier for older data: $250/month. Data transfer out: another $1,500/month because users download frequently.

The math after switching to MinIO

AWS S3 Monthly

  • Storage: $1,400
  • Requests: $300
  • Egress: $1,500
  • Total: $3,200

MinIO Self-Hosted

  • Servers (4 nodes): $800
  • Storage (100TB raw): $600
  • Bandwidth: $400
  • Total: $1,800

Annual savings: $16,800. ROI on server investment: 4 months.

And here's the key: zero code changes. MinIO implements the complete S3 API. Our applications kept using the same AWS SDK, just pointing to a different endpoint.

What MinIO actually is

MinIO is an object storage server released under GNU AGPLv3. It's S3-compatible - meaning it speaks the same API as AWS S3, Google Cloud Storage, and any other S3-compatible service.

100% S3 Compatible

Drop-in replacement for AWS S3. Same SDKs, same commands, same behavior.

High Performance

Written in Go. Handles millions of objects per cluster. Streaming and concurrent access optimized.

Distributed

Erasure coding for data protection. Scale out by adding more servers. No single point of failure.

How it differs from traditional file storage

Object Storage (MinIO/S3)

  • • Flat namespace (buckets/objects)
  • • Metadata with each object
  • • Scales to billions of objects
  • • HTTP-based access
  • • Built for unstructured data

Traditional File Systems

  • • Hierarchical (directories/files)
  • • Limited metadata
  • • Performance degrades with count
  • • POSIX/FTP protocols
  • • Built for structured data

Deployment: From single node to distributed cluster

1

Single node for development/testing

Quick start with Docker:

docker run -p 9000:9000 -p 9001:9001 \ -e "MINIO_ROOT_USER=admin" \ -e "MINIO_ROOT_PASSWORD=changeme" \ quay.io/minio/minio server /data --console-address ":9001"

Access UI at http://localhost:9001. API at http://localhost:9000. Not production-ready but great for testing.

2

Distributed setup for production

4-node cluster with erasure coding (recommended minimum):

# Node 1 minio server http://node1{1...4}/data{1...4} \ --address ":9000" \ --console-address ":9001"

Each node has 4 drives. Erasure coded across 16 drives total. Can lose any 4 drives without data loss.

3

Docker Compose deployment

docker-compose.yml for local cluster:

version: '3.8' services: minio1: image: quay.io/minio/minio command: server http://minio{1...4}/data{1...4} environment: - MINIO_ROOT_USER=admin - MINIO_ROOT_PASSWORD=changeme volumes: - data1-1:/data1 - data1-2:/data2 - data1-3:/data3 - data1-4:/data4 ports: - "9001:9000" - "9011:9001" # Repeat for minio2, minio3, minio4... # Each with different ports and volume mappings
4

Kubernetes deployment (recommended for scale)

Using Helm chart for production:

# Add MinIO Helm repo helm repo add minio https://charts.min.io/ helm repo update # Install with custom values helm install minio minio/minio \ --set mode=distributed \ --set replicas=4 \ --set drivesPerNode=4 \ --set persistence.size=100Gi \ --set rootUser=admin \ --set rootPassword=changeme \ --set consoleService.type=LoadBalancer

Data redundancy and erasure coding

MinIO uses erasure coding instead of traditional RAID. This is important because it offers better performance and flexibility.

EC:4 (standard)

Splits data into 4 chunks, creates 4 parity chunks. Can lose any 4 drives. Storage overhead: 2x. Good balance of safety and efficiency.

EC:2 (space-optimized)

Splits data into 8 chunks, creates 2 parity chunks. Can lose any 2 drives. Storage overhead: 1.25x. Maximum space utilization.

EC:8 (maximum redundancy)

Splits data into 4 chunks, creates 8 parity chunks. Can lose any 8 drives. Storage overhead: 3x. For critical data.

Recommendation: Start with EC:4 on a 4-node cluster. Gives you drive-level redundancy and node-level redundancy. Can rebuild from any 3 nodes if one fails completely.

Migrating from AWS S3 to MinIO

The beauty of S3 compatibility: your applications don't know the difference. Here's how we migrated.

Step 1: Set up MinIO and create buckets

# Using MinIO Client (mc) mc alias set local http://minio.example.com admin password # Create buckets matching your S3 setup mc mb local/user-uploads mc mb local/backups mc mb local/assets

Step 2: Mirror data from S3

# Add S3 as a remote mc alias set s3 https://s3.amazonaws.com AWS_ACCESS_KEY AWS_SECRET_KEY # Mirror buckets mc mirror s3/user-uploads local/user-uploads mc mirror s3/backups local/backups # Watch for changes and sync continuously mc mirror --watch s3/user-uploads local/user-uploads

Step 3: Update application configuration

# Python boto3 example import boto3 s3 = boto3.client('s3', endpoint_url='http://minio.example.com', aws_access_key_id='admin', aws_secret_access_key='password' ) # Everything else stays the same s3.upload_file('file.txt', 'user-uploads', 'file.txt')

Change endpoint URL and credentials. Same SDK, same code, same behavior.

Step 4: DNS cutover (zero downtime)

# Create CNAME record s3.mycompany.com → minio.example.com # Or use proxy pass in nginx location /s3/ { proxy_pass http://minio-cluster:9000; }

Applications keep using s3.mycompany.com. Under the hood, it hits MinIO instead of AWS.

Production considerations I learned the hard way

Disk space monitoring is critical

Erasure coding needs free space to rebuild. At 85% capacity, performance degrades. At 95%, writes start failing.

# Set up alerts mc admin trace local --verbose --watch # Check disk usage mc admin info local

Network latency matters between nodes

Spread nodes across availability zones? Expect slow rebalancing. Keep nodes in same datacenter, different racks.

Backup MinIO itself

MinIO stores metadata and configuration. Back these up regularly:

# Backup .minio.sys directory mc cp --recursive local/.minio.sys /backup/minio-config/ # Or export IAM policies and buckets mc admin info local > cluster-state.txt

Use SSDs for metadata, HDDs for data

Mixed storage tiering keeps costs down while maintaining performance for critical operations.

When MinIO doesn't make sense

Small scale (< 1TB)

Setup overhead outweighs savings. Just use S3 or B2.

Spiky, unpredictable traffic

Cloud auto-scales better. You'd over-provision for peaks.

No operations team

Someone needs to manage hardware, updates, failures. Managed services might be cheaper overall.

Large, predictable storage needs (10TB+)

Break-even happens quickly after initial investment.

Data privacy requirements

Keep data on your own infrastructure. Full control over encryption and access.

Frequent data access (high egress)

No data transfer fees. Bandwidth is your biggest cost with cloud providers.

Why we made the switch

At 50TB and growing, the math was undeniable. $16,800 annual savings, zero application changes, complete control over our data. The migration took one weekend. We've been running MinIO in production for 18 months with zero downtime.

Is it more work than managed S3? Yes. We monitor disk health, we handle drive replacements, we manage updates. But for that work, we save $1,400 every month and own our infrastructure completely.

If you have substantial storage needs and basic operations capability, MinIO is worth serious consideration. The S3 compatibility means you can always switch back if needed - but I doubt you will.