Replacing MinIO: open source is a license, not a promise

In October, while planning the installation package of a product I work on, I ran a simple check: can the container images the installer pulls still be pulled from a machine that has never pulled them before? The other images came down fine. For MinIO’s two images, the registry answered that no such image existed.

On my own computer everything had been working for months, because those two images were sitting in Docker’s cache. Without my noticing, the product had become one that only ran on machines where it had already been installed.

This post covers three things: what happened to MinIO step by step, how I solved the problem in a product that depended on it, and what all of this made me think about open source and dependencies. I won’t name the product, because the product isn’t the point. The point is a situation that can happen to anyone who leans on an open-source project.

What the product used MinIO for

The product is an analytics application that organizations install on their own servers. A user uploads a log file; the application keeps the original as it is, parses its content and writes the results to PostgreSQL. MinIO did the first half of that: it held the original uploaded files.

A few years ago nobody would have questioned that choice. MinIO was an object store compatible with Amazon S3, it came up as a single container, it was open source, and it appeared in almost every sample docker-compose.yml you could find. The application touched it through just five functions: upload, download, read as text, split into lines, and delete.

Keep those five functions in mind. That narrow interface is what made my life easy at the end of this story.

What happened to MinIO, step by step

People who want to sum it up in one sentence say “MinIO went paid.” That sentence doesn’t quite describe what happened. The source code is still on GitHub and its license is still AGPLv3. What changed is everything around the code: maintenance, prebuilt binaries, container images, the admin console and security fixes. They didn’t go in a day; they went one step at a time.

Date What happened
May 2021 The server’s license changed from Apache 2.0 to AGPLv3.
November 2024 The company announced AIStor, its commercial product. The announcement presented it as a store built for AI and data workloads.
May 2025 The administration functions were removed from the community edition’s web console; the release notes pointed to the commercial product for them.
October 2025 The README said the community edition was now distributed as source code only. The security fix released the next day shipped without prebuilt binaries or an official image. It became the last community release.
December 2025 The README declared “maintenance mode”: no new features or contributions would be accepted.
February 2026 A notice that the repository was no longer maintained was put at the top of the README, and the repository was archived.
March–April 2026 Seven security advisories affecting the community edition were published; none of them got a community release.
September 2026 First the images disappeared from Docker Hub; the last capture of the page, a few days before the removal, showed about 1.85 billion pulls. Then the images on quay.io stopped serving anonymous pulls. I could not find an announcement from MinIO about the removal of the images.
A grid showing that the MinIO community edition's source code stayed in place while the admin console, new binaries and images, security fixes and the ability to pull old images were withdrawn one after another between 2025 and 2026
The source code stayed at every step; what left was everything that made it usable.

Is “it went paid” accurate?

The company’s pricing page states it plainly today: the open-source edition has reached end of life and gets no more bug fixes or security patches, and its users are advised to evaluate alternatives or to talk to the company about the commercial product.

That commercial product, AIStor, has a free tier; but it isn’t free in the free-software sense. Its license agreement allows the software to run on a single node only, forbids distributing it to third parties, and the software does not run without a license file. There are paid tiers for those who want a distributed setup.

If you run the software by yourself on your own server, those terms may not bother you. In my case the product carried MinIO to other organizations inside its installation package. With a license that forbids redistribution, I couldn’t do that.

What we missed on our side

My own part of the story is less flattering. Looking back through the repository history, I can see three separate warnings that we missed.

The first: MinIO was added to the product in April 2026. By then MinIO’s repository had been archived for about two months, and its README said the repository was no longer maintained. Nobody looked, because the image pulled and everything worked.

The second: In mid-September, the minio/minio:latest image in the compose file could no longer be pulled. I investigated, saw that the images were gone from Docker Hub, pointed the reference to quay.io and pinned the version. I even noted in the commit message that MinIO had moved its image distribution to quay.io. I had treated the symptom; I never asked why the image had disappeared.

The third: Eleven days later, the images on quay.io stopped serving anonymous pulls too. I didn’t notice for a month, because thanks to my cache nothing broke.

The first lesson I took from this: a cache is a false witness. “It works on my machine” doesn’t prove your dependency still exists; it only proves that it existed once.

TIP

Add a small check that regularly tries to pull every image and package your installation needs from an environment with an empty cache. That check would have told me about this weeks earlier.

First things first: secure what you have

Before I knew which solution I would choose, the first thing I did was export the two cached images into a single archive file with docker save. From that moment, that 85 MB file was the only guaranteed way to reach the old data. MinIO keeps files on disk in its own format; the program that can safely read them is MinIO itself, and that program could no longer be downloaded from its official locations.

When you notice a dependency has vanished, your first reflex is to look for a new one. I think the right order is this: first secure the last working copy you have, then think.

How I narrowed down the options

The rule I set for myself was simple: first look for an alternative that is free and genuinely open; if nothing fits and the job isn’t large, write it yourself.

I judged the candidates with these questions:

  • License and redistribution: Can I hand this component to another organization inside an installation package? Distributing an AGPL-licensed image brings an obligation to offer the source, and many organizations’ open-source policies restrict AGPL.
  • Availability: Can the image be pulled today without authentication? That was the problem that brought me here.
  • Maintenance and single-vendor risk: How many people develop the project, who governs it, and does the contributor agreement allow the license to be changed?
  • Security advisories: How many have been published, and how quickly were they fixed?
  • Footprint: Does it add another container, port, password and memory limit?
  • Backup and migration: How do you back it up, and how do the existing files get to the new place?

I checked thirteen options one by one. The table below shows the state at the end of October 2026 and the conclusion I reached for my own need; if your need is different, your conclusion may be too.

Option License What I saw Outcome
AIStor Free Commercial (proprietary) Every installation needs a license file, single node only, redistribution forbidden. Ruled out.
MinIO forks (PGSTY Silo, Chainguard) AGPLv3 Security fixes continue; each depends on a single organization. A temporary bridge only.
versitygw Apache 2.0 A small S3 gateway that keeps objects as plain files on disk; it asks for no license assignment. The best free server.
RustFS Apache 2.0 The new engine closest to MinIO; very young, and its contributor agreement lets the company change the license. Not chosen.
SeaweedFS Apache 2.0 Very capable and made of many components; too wide a surface for a few GB of files. Not chosen.
Garage AGPLv3 Small and independent; its own documentation considers single-copy use fit for testing only. Not chosen.
Ceph LGPL A cluster designed for real scale; too much for one server. Out of scale.
Keeping the files in PostgreSQL No new component One backup and transactional consistency; but every backup grows with the files. Not made the default.
My own disk store My own code A disk store of under 300 lines; no extra container, port or password. This is what I chose.

I ran the research together with AI agents and set it up in two stages: the first stage surveyed the options, and the second stage tried to refute the first stage’s findings. The second stage brought two more options to the table, ruled one out, and found two defects in my own design. I’ll describe those two defects below, because they are the most instructive part of this post.

The solution: I didn’t replace the dependency, I removed it

While comparing alternatives, I realized I had asked the real question late: do I actually need an object store?

The product runs on a single server, as a single process. Files are written once and never change. The total size doesn’t go beyond a few GB. Under those conditions, what the object store gave me was what the file system already gives; and in exchange I was getting a separate container, two ports, a pair of credentials, a 512 MB memory limit and a stream of security advisories to follow.

In the end I did this:

  • Behind the same five functions, I wrote a small store that keeps the files as plain files on a Docker volume. The disk store itself is under 300 lines; with the S3 backend, the cache and the error handling, the whole module is about a thousand.
  • I didn’t throw away the existing S3 code; I reworked it into an optional second backend. An organization that has its own S3 store can have the files written there with a single setting.
  • I didn’t touch the layout of the file names or the pointer column in the database. That left nothing to migrate on the database side.

The interface looks roughly like this:

class BlobStore(Protocol):
    def put(self, key: str, data: bytes) -> None: ...
    def get(self, key: str) -> bytes: ...
    def read_range(self, key: str, offset: int, length: int) -> bytes: ...
    def exists(self, key: str) -> bool: ...
    def delete(self, key: str) -> None: ...
Before: the application writes through a five-function S3 client to a MinIO container and its volume. After: the application writes through the same five-function store interface to a disk store by default, and optionally to the organization's own S3 store
The rest of the application noticed nothing; only what sits behind the five functions changed.

“Writing to a plain file” sounds simple. It isn’t; a few details the object store used to handle for you are now yours:

  • Atomic writes: I first write the file under a temporary name in the same folder, make sure it has reached the disk (fsync), then move it to its real name in one step. If the power goes out halfway, no half-written file is left behind.
  • Path safety: The store accepts only keys in the form it generates itself, and it doesn’t follow symbolic links. So no key can escape the store’s folder.
  • Clear errors: A missing file returns 404, a full disk 507, an unreachable S3 store 503. All of these used to be a meaningless 500.

The two defects the second review found were just as instructive as those details:

  1. The delete order. The old code deleted the file first and the database row afterwards. If an error got in between, you were left with a record that had no file. I reversed the order: the row’s deletion is committed first, and only then is the file removed. An orphaned file is harmless; a record without its file is not.
  2. Testing against a real S3 server. Not every “S3-compatible” server behaves the same; for example, there is no guarantee that every server accepts the checksums the client library adds by default. So the S3 backend is tested against a real S3 server in CI on every change, and that behavior is configurable.

The migration: without burning bridges

With the new store written, what remained was moving the existing files. I had one principle here: I would take no step I couldn’t come back from.

  • The migration is a separate command that you run by hand. The application migrates nothing on its own at startup; if it sees missing files, it only warns and names the command to run.
  • The command starts with a dry run: how many files are there, how many bytes will be copied, is there enough room on the target?
  • In the real run, every file is copied, read back from the target, and its sha256 digest is compared with the source’s. The results go into a manifest file, and a separate verify command checks that manifest again.
  • The command deletes nothing and never overwrites an existing file. Run a second time, it copies nothing.
  • I rehearsed all of it on a throwaway copy of the installation first, and then ran it on my own.

The old MinIO volume, the image archive and the rollback steps are all still there. They will stay for at least one release. Between trusting a new system that works and deleting the old one, nothing calls for hurry.

What I gained, and what I took on

What I gained:

  • The installation now pulls only images that anyone can pull.
  • One service and one init job are gone; two ports and a pair of credentials went with them.
  • The installation package shrank by 85 MB, and the containers’ total memory limit dropped by 512 MB.
  • The package no longer contains an AGPL-licensed component or a known vulnerability that security scans flag.
  • A backup is one database dump plus one tar file.

What I took on:

  • The durability details are my responsibility now: atomic writes, a full disk, path safety.
  • There is no ready-made S3 endpoint for other tools on the same machine.
  • Existing installations have to run a one-time migration.

There are also cases where this solution would be wrong, and I wrote them down from the start: if the application has to run on more than one server, if immutable retention (WORM) is required, or if the organization’s policy demands object storage, the right answer isn’t the disk store but the S3 backend. That is why I left that door open.

If you really do need an S3 server

My solution doesn’t fit everyone. If you have more than one server, if your files run into terabytes, or if the tools you use expect an endpoint that speaks S3, then you do need an object store. In that case, “what replaces MinIO” is first a question of “which one will still be here on the same terms in five years,” and only then “which one is faster.” A feature list doesn’t answer that; the license, the governance and the revenue model do.

Here are the candidates I looked at in late October 2026, and what I saw in them:

  • versitygw (Apache 2.0): a small gateway that maps S3 calls onto a folder. Your objects sit on disk as plain files; so even if versitygw went away tomorrow, your files would still be in your hands. It asks for no license assignment on contributions. If I needed an S3 endpoint on a single server, this is the first place I would look; I also run my S3 backend’s tests against it.
  • Garage (AGPLv3): developed by an association rather than a company, funded by public grants, and designed for small, geographically distributed setups of a few nodes. Its own documentation does not recommend a single-node setup for production. AGPL needs care if you plan to distribute it with your product.
  • SeaweedFS (Apache 2.0): very capable and moving very fast; but in the last three months two thirds of the contributions came from one person, and there is a separate, proprietary enterprise edition.
  • RustFS (Apache 2.0): the new engine that resembles MinIO most, and many projects put it in place of MinIO in their tests. But its first stable release came out in September 2026, and its contributor agreement gives the company the right to distribute contributions under a different license. That is a door worth thinking about.
  • Ceph (LGPL): developed under the Linux Foundation, with more than two hundred people contributing in the last three months. The right answer for real scale; too heavy for a single server. At the same scale there is also Apache Ozone, under the Apache Software Foundation.
  • MinIO forks: PGSTY Silo brought the admin console back and keeps the security fixes coming; Chainguard publishes a fork that receives security fixes only, with a free image. Both are AGPL and both depend on the effort of a single organization. I see them not as a destination but as a bridge to use while you move.

Dozens of security advisories were published this year for both RustFS and SeaweedFS. That is no surprise for projects that grow fast and are examined closely; but whichever you choose, you will have a stream of advisories to follow.

A three-question decision flow: plain files behind a narrow interface for one server and write-once files; the organization's own S3 store if there is one; otherwise versitygw, Garage or SeaweedFS depending on scale, and Ceph at large scale
This is the order I decide in; if your questions are different, your answer will be too.

I also looked at what other projects did, and saw that there is no single answer. GitLab removed the bundled MinIO from its Helm chart and put nothing in its place: you bring your own S3 store. NixOS moved its object-storage test to Garage. Apache Iceberg switched to RustFS in its tests, Grafana Loki to Silo in its sample setups, and Zalando Spilo to SeaweedFS. Incus took a path similar to mine and replaced the feature that relied on MinIO with its own built-in S3 endpoint. I saw the same spread when I looked at forty recently merged changes on GitHub whose titles say they replace MinIO: twelve chose RustFS, eleven SeaweedFS, four Garage and four the plain disk.

How the AI era changed this picture

Here I want to keep fact and interpretation apart.

The fact: MinIO’s commercial product is called AIStor, and the company presents it as a data store built for AI workloads. As the community edition was wound down, the README and the release notes pointed readers to that product again and again. For the removal of the images, no reason was given.

My interpretation: the money flowed to AI, and infrastructure companies repositioned their products toward where the money flows. For an object store, the most valuable customer is no longer a small team keeping a few GB of files; it is an organization storing training data at very large scale. In the eyes of such a company, a community edition is not a source of revenue but a cost. That isn’t a betrayal; it is a business decision. But the ones who live with its consequences are those who built products on top of that edition.

The same era changed the math on my side too. Writing a narrow component yourself doesn’t cost what it used to. My paper estimate for this job was four to five focused days; working with AI agents, the code was merged into the main branch and my own installation was migrated the day after the decision. For many things about which I used to say “I won’t write that myself, I’ll use what’s out there,” the scale no longer sits in the same place.

The pressure doesn’t come from one direction either. When Cal.com closed its production code in 2026, the reason it gave was AI: it said that tools can scan an open codebase systematically and find vulnerabilities. The pressure on open source no longer comes from the revenue model alone.

There is another side to the coin. When AI assistants suggest dependencies, they mostly suggest yesterday’s popular answer. Ask an assistant today where to keep your files, and you will most likely get the answer that appeared most often in the sample files of a few years ago. Checking whether the suggested dependency is still standing today is your job.

What I think about open source now

First I should say this: it isn’t only MinIO’s story. In recent years we have seen two kinds of similar decisions. The first is a license change: HashiCorp changed its license in 2023, and Redis in 2024. The second is withdrawing the distribution: in 2025 Bitnami moved its prebuilt images to a repository that gets no updates and put the current ones behind a subscription; in 2026 LocalStack ended its community image and archived its repository. In the second kind the license never changes; MinIO was of that kind.

What makes the difference is what follows the decision. Valkey, the fork of Redis, was set up within eight days under the Linux Foundation with large backers; for OpenTofu, the fork of Terraform, it took about six weeks. Behind each of MinIO’s forks there is a single organization. Code is not enough for a fork to live; somebody has to take ownership of it.

The NATS case shows best what a foundation protects: in 2025, when the company behind the project wanted to change the license, it could not do so on its own, because the project sat under the foundation.

Here is what I take from all of this:

Open source is a license, not a promise. A license gives you the right to use, modify and distribute the code that exists. It says nothing about whether a new release will come out tomorrow, whether prebuilt binaries will be published, or whether the image will stay at the same address. No license contains the sentence “it will still be here tomorrow.”

But a license is a right to exit. MinIO’s license stayed AGPL to the end, and the code is still there. The forks that keep the security fixes coming today exist because of that license. Had the same thing happened to a closed product, I wouldn’t even have code to read. Open source doesn’t give you a service guarantee; it gives you the key to the exit door.

Nobody owes us anything. For years MinIO gave away very good software for nothing, and many people, me included, built work on top of it. I have no right to demand that something I got for free stays free forever. The responsibility for a product belongs to whoever makes it; I was the one who chose the dependency, and I was the one who should have kept an eye on it.

A dependency is not just code. A dependency is made of four things: the code, the distribution channel that gets that code to you, the maintenance that keeps it alive, and the governance that decides who gets to decide all of that. Most of the time we look only at the first. In MinIO’s case the code never changed; the other three left.

Single-vendor open source and community-governed open source are not the same thing. If a project’s trademark, its repository and the rights to its contributions all sit with one company, the project changes the day that company’s priorities change. In projects under a foundation or an association, that decision isn’t in a single hand. Both are open source; but the assurance they give you is not the same.

The best dependency is one that sits behind a narrow interface. The only reason I could make this change in a few days is that the application touched MinIO through five functions. Had the same calls been spread across fifty places in the code, this post would be much longer and much sadder.

Lean on standards, not on products. Because S3 stopped being a product and became a protocol, there are half a dozen servers I could put in MinIO’s place today. The same goes for the file system. If your dependency is a standard, you will find another implementation when the one you use goes away.

What I ask before adding a new dependency

Since this happened, I ask myself these questions before adding a dependency:

  1. Can I cover this need myself with the standard library, or with a few hundred lines?
  2. Who governs the project: a single company, or a foundation or an association?
  3. Does the contributor agreement let the project’s owner change the license on its own?
  4. Where does the company make its money, and does the way I use the project stand in the way of that?
  5. Who distributes the image or the package; if that address closes tomorrow, what am I left with?
  6. Is my installation tried regularly on a machine with an empty cache?
  7. From how many places in the code do I touch this dependency?
  8. Can I take my data out of it and move it somewhere else?

None of the eight questions is technically hard. The hard part is remembering to ask them while everything works.

If you have been through something similar, or chose a different path away from MinIO, I’d like to hear about it below; I’m genuinely curious which questions other people asked.