Self-hosting MinIO: AWS S3 but Without The Crazy Prices

Sethonne July 07, 2025

As I wrote before, what works today for 200 users might need reevaluation at 2,000. But sometimes, the old-school approach of owning your infrastructure can be exactly what your application needs to thrive.

Self-hosting MinIO: AWS S3 but Without The Crazy Prices

I may not be the most experienced AWS practitioner in the world or even at all, but I will hold my chest high when I tell you that I have thoroughly eliminated the need to pay for AWS S3 storage, in my use case at the very least. With users uploading increasingly large PDF files in a website I made and my application serving maybe hundreds of document previews monthly, it was time to explore a method that did require me to open up my wallet.

Enter MinIO: an open-source, S3-compatible object storage server that promised to deliver the same functionality at 0 cost because I was self-hosting it. After three months of running MinIO on a self-hosted VPS, I can confidently say this was one of the best technical decisions I've made for my application.

The AWS S3 Cost Creep

My application allows users to upload PDF documents, generate reports, and download various file formats. Initially, with just a handful of beta users, S3 seemed like the obvious choice. The AWS free tier was generous, the SDK was well-documented, and the "pay-as-you-go" model felt reasonable (under the assumption that my application was self-sustainable and made as much money as it costed).

But as my user base grew, these were the numbers I would have been paying had I used AWS S3:

  • Storage costs: $0.023 per GB per month (Standard tier)
  • GET requests: $0.0004 per 1,000 requests
  • PUT requests: $0.005 per 1,000 requests
  • Data transfer: $0.09 per GB for the first 10TB

While it might not sound like much, I am a cheapskate and I do not want to have to pay what I don't have to. More importantly, these costs would grow linearly with user adoption, creating an unsustainable trajectory for a bootstrapped project.

Discovering MinIO: The S3-Compatible Alternative

MinIO caught my attention because it promised S3 compatibility while running on my own infrastructure. The value proposition was compelling:

  • S3-compatible API: Existing code could work with minimal changes
  • Self-hosted: Complete control over data and infrastructure
  • Cost-effective: Only pay for the VPS hosting it
  • High performance: Optimized for modern hardware
  • Open source: No vendor lock-in

The technical architecture was straightforward: MinIO runs as a single binary that can be deployed on any server with persistent storage. That piece of information isn't that relevant to me though since I just use Coolify to deploy it anyway. It exposes the same REST API as S3, meaning any existing AWS SDK calls would work unchanged.

It Was Pretty Easy to Migrate To

One of MinIO's biggest advantages is its S3 compatibility. My existing upload service required minimal changes:

// Before (AWS S3)
const s3Client = new S3Client({ 
  region: 'us-east-1',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  }
});

// After (MinIO)
const s3Client = new S3Client({
  region: 'us-east-1', // MinIO doesn't care about region
  endpoint: 'https://minio.mydomain.com',
  credentials: {
    accessKeyId: process.env.MINIO_ACCESS_KEY,
    secretAccessKey: process.env.MINIO_SECRET_KEY,
  },
  forcePathStyle: true, // Required for MinIO
});

The only significant change was adding forcePathStyle: true and updating the endpoint URL. All my existing S3 operations—uploads, downloads, presigned URLs—worked identically.

Performance Comparison: The Numbers Don't Lie

After three months of running MinIO, the performance metrics were impressive:

Response Times

  • File uploads: 1.2s average (vs 1.8s with S3)
  • File downloads: 0.8s average (vs 1.4s with S3)
  • Presigned URL generation: 15ms average (vs 45ms with S3)

The improved performance was largely due to:

  1. Reduced latency: No cross-region AWS calls
  2. Optimized network path: Direct connection to my server

Storage Efficiency

MinIO's storage efficiency metrics were also impressive:

  • Compression: 15% average file size reduction
  • Deduplication: Eliminated 8% of redundant data
  • Disk usage: 425GB actual vs 450GB logical (94% efficiency)

The Pros and Cons: A Balanced Perspective

Advantages of Self-Hosted MinIO

Cost Effectiveness: The most obvious benefit. For applications with predictable storage needs, the fixed VPS cost is significantly cheaper than cloud storage pricing.

Performance: Geographic proximity and optimized network paths resulted in measurably faster file operations.

Data Sovereignty: Complete control over data location and access policies. This was particularly important for my application's compliance requirements.

Customization: Full control over storage configurations, backup strategies, and security policies.

No Vendor Lock-in: Open-source solution means no proprietary dependencies or unexpected pricing changes.

Predictable Costs: Fixed monthly VPS cost regardless of usage fluctuations.

Disadvantages and Challenges

Infrastructure Responsibility: You're now responsible for server maintenance, security updates, and monitoring. This adds operational overhead that cloud providers typically handle.

Single Point of Failure: Without proper redundancy, your VPS becomes a critical dependency. I mitigated this with automated daily backups to a separate cloud provider.

Limited Scalability: While MinIO supports clustering, scaling requires additional infrastructure planning and costs.

Backup Complexity: Implementing robust backup and disaster recovery strategies requires additional tooling and processes.

Monitoring and Alerting: Setting up comprehensive monitoring for disk space, performance, and availability requires additional configuration.

Security Responsibility: You're responsible for server security, SSL certificates, and access controls.

React Query Integration

The migration also provided an opportunity to improve my frontend architecture with React Query caching:

const useFiles = () => {
  const filesQuery = useQuery({
    queryKey: ["files"],
    queryFn: fetchUserFiles,
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 10 * 60 * 1000, // 10 minutes
  });

  const uploadMutation = useMutation({
    mutationFn: uploadFiles,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["files"] });
    },
  });

  return { filesQuery, uploadMutation };
};

This caching layer reduced API calls to MinIO by approximately 60%, further improving performance.

Lessons Learned and Best Practices

When MinIO Makes Sense

Self-hosted MinIO is ideal for:

  • Predictable storage needs: Applications with relatively stable storage requirements
  • Cost-sensitive projects: Bootstrap projects where every dollar counts
  • Data sovereignty requirements: Applications needing complete control over data location
  • Performance-critical applications: Where reduced latency matters
  • Compliance requirements: Specific regulatory or audit requirements

When to Stick with Cloud Storage

AWS S3 or similar cloud services are better for:

  • Highly variable workloads: Unpredictable storage or traffic patterns
  • Global applications: Multi-region deployments with diverse user bases
  • Rapid scaling needs: Applications expecting exponential growth
  • Minimal DevOps resources: Teams without infrastructure management expertise
  • High availability requirements: Mission-critical applications needing 99.99%+ uptime

Migration Best Practices

  1. Start small: Begin with a development environment to test compatibility
  2. Gradual migration: Avoid big-bang deployments; migrate incrementally
  3. Monitor everything: Set up comprehensive monitoring before going live
  4. Have a rollback plan: Keep the old system running during initial deployment
  5. Test thoroughly: Validate all file operations work correctly
  6. Document everything: Create runbooks for common operational tasks

The Verdict: Context Matters

Three months into my MinIO journey, I can confidently say it was the right choice for my specific context. The cost savings alone justified the migration, but the performance improvements and increased control over my data infrastructure were equally valuable.

However, this isn't a universal recommendation. The decision between cloud storage and self-hosted solutions depends heavily on your specific requirements:

  • Team expertise: Do you have the skills to manage infrastructure?
  • Cost sensitivity: How much do storage costs impact your bottom line?
  • Scale requirements: Will you need to scale beyond a single server?
  • Compliance needs: Do you need specific data sovereignty guarantees?
  • Performance requirements: Is latency a critical factor for your users?

For my document generation platform with predictable storage patterns, self-hosted MinIO checked all the boxes. The fixed monthly cost provides budget predictability, the performance improvements enhance user experience, and the complete control over data infrastructure aligns with my application's compliance requirements.

The journey from AWS S3 to self-hosted MinIO taught me that "best practices" are often contextual. While cloud storage is undoubtedly the right choice for many applications, there's still a place for self-hosted solutions when the requirements align.

As my application continues to grow, I'll undoubtedly face new challenges that may require architectural changes. But for now, MinIO has proven to be a cost-effective, performant, and reliable foundation for my file storage needs.

As I wrote before, what works today for 200 users might need reevaluation at 2,000. But sometimes, the old-school approach of owning your infrastructure can be exactly what your application needs to thrive.

Let's take this to your inbox.

Sign up to my newsletter!

[email protected] Subscribe