Hacker News new | past | comments | ask | show | jobs | submit login
MinIO passes 1B cumulative Docker Pulls (min.io)
134 points by edogrider on Sept 21, 2022 | hide | past | favorite | 66 comments



I wonder what Docker's peak bandwidth usage is, and how they make it work, financially.

Just imagine the vast number of poorly cached CI jobs pulling gigabytes from Docker hub on every commit, coupled with naive aproaches to CI/CD when doing microservices, prod/dev/test deployments, etc.


It's been 2 years now but Docker has started cracking down on this [1]. They introduced pull limits for anonymous and free accounts. Our company actually hit the anonymous limit regularly (several separate teams using docker/CI with the same public IP from our datacenter.) We've since started caching our images locally using Sonatype Nexus Repository Manager plus hosting our own registry for some simple things we used to be pulling from Docker Hub.

As far as financially, Docker now charges for enterprise use of Docker Desktop, which we've also started paying for. But I'm sure the bandwidth for running Docker Hub isn't cheap.

[1] https://docs.docker.com/docker-hub/download-rate-limit/


I have thought there needs to be much more trivial plug-and-play caching solution that works for the major services: npm, pypi, cargo, docker, etc. Right now, is is justifiably annoying enough that nobody worries about it until they are squandering terabytes of bandwidth or dealt with an external outage.


It's called HTTP. The problem with all these services is they're using HTTPS for encryption and verification.

Which breaks all normal caching proxies that can be easily used.

The popular packaging formats are better at this: distribute keys over https, then just http to download and verify packages. Squid and co work just fine here.


No one will bother putting in the effort until usage quotas and costs get involved. While repos allow unlimited free pulls, it makes no sense for users to spend a minute caching anything.

Once quotas come in, you’ll find all the tooling and guides make it super simple.


> No one will bother putting in the effort until usage quotas and costs get involved. While repos allow unlimited free pulls, it makes no sense for users to spend a minute caching anything.

I don't know about that. One of the benefits of introducing something like Nexus can be a noticeable speedup for your own builds, once your proxy repositories have the versions of dependencies you need cached.

Of course, you could use some sort of a local build cache (e.g. m2 directory for Maven packages on the server) but when you're building your own containers it doesn't always turn out to be as viable, especially when you have many CI nodes, each of which would have a local build cache.

On an unrelated note, something like Nexus also gives you the ability to easily start deploying/using your own container images, libraries or even arbitrary files, all without having to store your data in the cloud, or figure out how many different solutions/accounts you might need (otherwise you'd need some packages on Docker Hub, some Node packages on npm, some Java packages in Maven repos etc.).


There are very unsexy ways to reduce pull behaviour. In a previous life I built a CI/CD pipeline for an AI company and some of the container images were huge (ie GBs) so I did a lot of work on image caching. Mainly breaking up large containers into layers, starting with large layers with longer shelf-life, and incrementally adding faster-changing or smaller layers on top. Any time a large layer changed, it would trigger a packer rebuild for the CI runner AMI. The AMI also contained a start-up service that discovered and refreshed base images at boot time so they would (hopefully) never delay a real CI job. The natural scale up/down and max lifetime of the runner pool guaranteed that we never had an old AMI in use. It's likely that most people wouldn't bother with that level of optimization (or even host their own runners) but we need it.


Artifactory and Nexus work great for that.


It would be lovely to see Bittorrent in this space. Distributing an identical blob to N machines is squarely within its wheelhouse. All those CI machines would make wonderful seeders too.


Uber Engineering open-sourced Kraken [1], their peer-to-peer docker registry. I remember it originally using the BitTorrent protocol but in their readme they now say it is "based on BitTorrent" due to different tradeoffs they needed to make.

As far as I know there aren't any projects doing peer-to-peer distribution of container images to servers, probably because it's useful to be able to use a stock docker daemon on your server. The Kraken page references Dragonfly [2] but I haven't grokked it yet, it might be that.

It has seemed strange to me that the docker daemon I run on a host is not also a registry, if I want to enable that feature.

It's also possible that in practice you'd want your CI nodes optimized for compute because they're doing a lot of work, your registry hosts for bandwidth, and your servers again for compute, and having one daemon to rule them all seems elegant but is actually overgeneralized, and specialization is better.

1 https://github.com/uber/kraken

2 https://d7y.io/


The gaming industry has been using peer to peer downloads for a long time AFAIK. Unsure if they still do, pretty sure the Battle.net client did it.


It was used in the WoW downloader pre-Battle.NET, they've moved off that and switched to CDNs (mostly Akamai IIRC) & dynamic patching, i.e.: Battle.NET computes differences and gets a diff.

Steam never used P2P downloading but they've had deals with ISPs and communities really early and made their own "CDN" full of caching servers everywhere, so if you waited a few hours to update, it might make it to your local server in time. Now I think they're on Akamai as well.


Oh, right, there was the WoW launcher before battle.net. I think I have another game in mind which used this, but I can’t really remember. Would love to get more insights on the economics of this (p2p vs. cdn).


WoW popularized it and a lot of “WoW killer” MMOs had P2P, but I never saw it “work” to be honest.


True, we have a few big inefficiences (on a local registry but still) that we didn't fix yet because no proper solution or higher priority tickets.. so CI just triggers loads of wasteful rebuilds.


...not to mention its environmental impact.

Docker requires to pull a 1GB image to run a 1MB binary.

Increases of 100x to 1000x are pretty common and moving data around is quite harmful.


Docker doesn’t require that. Many images are just poorly built.


Show me fully functional docker images that are 2MB in size...


There’s a huge gulf between 1GB and 2MB while still enjoying the benefits of containers. Go containers I build are <15MB and most of that is the fat Go binary.


Just slightly over 2MB nowadays:

  gcr.io/distroless/static-debian11   latest    10d0cc57bead   2.36MB
No functionality beyond running a binary, but, hey, that's what GP wanted.


And yet I clearly wrote "fully functional"...


I only use distro and scratch base image and build my own images locally from dockerfiles.


> Just imagine the vast number of poorly cached CI jobs pulling gigabytes from Docker hub on every commit, coupled with naive aproaches to CI/CD when doing microservices, prod/dev/test deployments, etc.

I hit the rate limits that others talk of in the comments, which motivated me to use Nexus for both proxying and storing my own container images. I actually tried using GitLab Registry before, but something with a bit more flexibility (cleanup policies) is nice! So far, it's been pretty good, I actually wrote about the process on my blog, "Moving from GitLab Registry to Sonatype Nexus": https://blog.kronis.dev/tutorials/moving-from-gitlab-registr...

Another thing that I tried, however, was to only rely upon Docker Hub for the base images that I want (Ubuntu in my case) and then build everything I need on top of that, doing things like installing Java/Node/Python/Ruby/... manually, adding utilities I want across all of the images etc. Once again, I wrote about it on my blog, "Using Ubuntu as the base for all of my containers": https://blog.kronis.dev/articles/using-ubuntu-as-the-base-fo...

That approach is absolutely more work, but also is something that's underexplored and works really nicely for me. Now I mostly rely on the OS package manager repositories (or mirrors of those), put less load on Docker Hub, don't risk running into its rate limits and also have common base layers across most of the images that I build, which in practice means less data actually needing to be downloaded to any of the servers where I want to utilize my images.

Of course, the downside is that getting something like PHP running was an absolute pain (tried with Apache, didn't work for some reason, then moved over to Nginx), and I technically miss out on some of the more complex space optimizations because if you look at the Dockerfiles for some of the more popular images, like OpenJDK, you'll occasionally see some interesting approaches, like getting the software package as a bunch of files and "installing" them directly, as opposed to using something like apt/yum: https://github.com/adoptium/containers/blob/08dd7d416cee0fe0...

Then again, personally I'd much prefer to rely on packages that I can get from something like apt directly, even if some of those versions can be a bit older (or add the project's official apt repositories as needed). One exception for software I don't install myself: databases, due to how good the majority of images out there (e.g. MySQL/PostgreSQL) are already. I just proxy/re-host those with different tags as needed.


Please clarify what is "poorly cached" and what is your solution to be not so poorly cached in this context. Looks like you're overestimating blindly.


If you run two jobs right after each other and the machine tries to pull the image separately for each one and redownloads the whole thing, then that's a poor cache. Modern practices make this relatively easy to accomplish by accident.


Especially for Docker, which is typically a daemon that runs in a different execution context as the CI job, which means you cannot just tell the CI job to cache some directory. Podman makes this easier but isn’t used as widely as Docker is.


a sibling comment to yours[0], from 'treesknees, addresses what a decent caching setup for this looks like.

> We've since started caching our images locally using Sonatype Nexus Repository Manager plus hosting our own registry for some simple things we used to be pulling from Docker Hub.

[0]: https://news.ycombinator.com/item?id=32931044


And well-earned, too. MinIO is a really neat solution for keeping local data available via the same protocol as that in the remote S3 buckets. I do wish there was something below it, though, complexity-wise, just a little application that serves a directory as an S3-compatible bucket. MinIO can feel a little much for simple testing scenarios and the like.


Take a look at Garage, it's very nice and simple.

https://garagehq.deuxfleurs.fr/


What about Localstack?

I haven't used it myself yet but discovered it the other day and want to use it for a test harness for an application I'm building.

https://hub.docker.com/r/localstack/localstack


The paywall decisions are sometimes kinda odd though. With SES you can only use the older API version, SESv2 is Pro only.


Another alternative:

https://github.com/gaul/s3proxy

(supports serving ("proxying") from file system)


Thanks!


It's a pity that S3 becomes some kind of standard object storage protocol. I think it's overcomplicated.


In what way do you think it’s over complicated? Genuinely interested.

Not sure about Minio, but most S3 clones don’t clone all API operations, just the ones they and their customers/users need.


1. Buckets is unnecessary concept. Why use it at all, when we have domain and path. That should be enough.

2. Authentication is too convoluted. Signings, etc. Simple `Authorization` header should be enough.

3. Uploading a file should be as simple as PUT /path/to/file. Instead of multipart upload, header `Range: bytes=0-1023` should be used.

4. List is unnecessarily flexible. Why should users have the ability to use any character as path delimiter. `/` should be fixed. List operation should use ordinary REST conventions, like `GET /dir/?from=token` should return JSON with items inside `dir` and continuation token.

Basically I want to be able to use object storage with simple `fetch` and few lines of code. Without all that SDK nonsense.


1. The bucket is the domain. If you have a self hosted, single tenant minio instance, you might not need buckets, it doesn't really hurt you to just set up one bucket, but for people that do need buckets, it would be a lot more difficult to build them on something that didn't have a similar concept.

2. I'm with you on this one. At least as long as it is over https. The signing process might make more sense in the context of plaintext http. And it might make the implementation of signed urls easier, idk. But, yeah definitely one of the rough points of the s3 api

3. An upload can just be a simple PUT in many cases. And for the cases where you do need a multipart upload: The Range header doesn't work the way you suggest, it is for requesting a range for the response, not giving a range for the data in the request. And even if you used a different custom header, you would need some way to indicate when you have uploaded everything. Maybe the API could be simplified a little bit, but I think quite a bit of it is essential complexity for the cases where you need multipart upload.

4. There are use cases where you want to use a different delimiter. And it's worth noting that as an object store, the data isn't really organized into folders, the / is just part of the object key. Definitely agree that it should support a JSON response though. And I do think it would make sense for / to be the default delimiter, because that is usually what people want.

> Basically I want to be able to use object storage with simple `fetch` and few lines of code. Without all that SDK nonsense.

If it weren't for 2, this would be possible. And that is a big reason why I wish the authentication was simpler.

All that said, I do wish there was a standardized specification for an object storage API, that all the different providers adhered to, rather than everyone making an "s3 compatible" API, where exactly what it means to be compatible with S3 varies from product to product.


> The Range header doesn't work the way you suggest, it is for requesting a range for the response, not giving a range for the data in the request

rfc9110 actually does permit servers to implement range on PUT, with partial update semantics, but warns that user agents can only safely do this if they know the origin server supports this, as if not the server could fallback to replacing the whole resource with just the uploaded range! (To my knowledge such support is not very common. Also due to risks of being misinterpreted by caches and other intermediaries, using the dedicated PATCH verb is probably better.)

But this (ranged with PUT) is only semantically appropriate for updating an existing valid resource. For example, if you have database like file format, you can upload only the changed ranges which could be much much smaller than the whole file. Or for something like a log file, one could append new content to the end.

For most files, breaking them into many parts for initial upload and range putting each part would be semantic inappropriate, since the resource is likely to only actually be valid once the last part is uploaded.


Every one of the things you list as unimportant, someone else lists as a mandatory requirement. And that’s why S3 won. No one uses everything it does, but everyone uses a different 10%.


I don’t think S3 won because of its protocol, but rather being the first in its kind and dominating the market. Then other vendors started competing products, and to easy transition, implemented (parts of) the S3 protocol.


It won because it was the first, but also because it’s API was designed to be cloned. Being cloned was a success criteria for the the S3 team.

I read that somewhere recently. I don’t have a source to hand.

Personally I this the API is ok, but if they did designer it to be cloned, they could have done it slightly better.


> Simple `Authorization` header should be enough.

Cool so now you can’t fetch non-public S3 objects in the browser unless you’re fetching over Ajax? What’s in the Authorization header - an API key? There’s a reason services serious about security don’t do it like that anymore.


You can use presigned urls for that.


Tech lead of Cloudflare R2 if that's helpful as I've thought about this a bit.

First things first, a bit of a shameless plug [1] to answer this piece:

> Basically I want to be able to use object storage with simple `fetch` and few lines of code. Without all that SDK nonsense.

      const object = await env.MY_BUCKET.put(objectName, request.body, {
        httpMetadata: request.headers,
      })
      return new Response(null, {
        headers: {
          'etag': object.httpEtag,
        }
      })
It's called `put` instead of `fetch` but basically same concept.

> Buckets is unnecessary concept. Why use it at all, when we have domain and path. That should be enough.

The utility is because it's important for accounting, organization, ACLs, jurisdiction controls, analytics, different business units needing different properties, etc. If you use a virtual-hosted URL then you have something like https://<bucket>.<account>.r2.cloudflarestorage.com/<path>. That's your domain + path. Buckets is still a useful higher-order organizational concept. FWIW Azure Blob storage and S3 were released at roughly the same time. Both had the same organizational concept. The other thing to think about is that folders are tree structures and that just doesn't have the same scaling properties to billions or trillions of files that a flat namespace does.

> Authentication is too convoluted. Signings, etc. Simple `Authorization` header should be enough.

Yeah probably. 16 years ago, HTTPS was a lot less common. Even still, some users do have legitimate concerns about middleware injecting headers / intercepting signed requests & replaying them. Sigv4 does address that in a comprehensive way. It also ensures some level of protection against corruption that can happen before TLS encryption and after TLS decryption.

> Uploading a file should be as simple as PUT /path/to/file. Instead of multipart upload, header `Range: bytes=0-1023` should be used.

I agree that multipart has various misdesigns. The one that actually really gets me is ListParts which lets you retrieve the original parts of a multipart file - like wtf that seems legit useless. At the time though (16 years ago now), resumable uploads didn't exist as a standard (unless you count WebDAV which I don't because it failed miserably). There's now an attempt to standardize something using TUS a starting point but I don't see much incentive for Amazon to really update S3 here because that's not where they're investing their R&D budget. It's up to the rest of us (particularly browser vendors) to create a standard and enough demand that they have to update their API. FWIW the range thing has to be careful because aside from resuming the other piece that's important for resumable uploads is concurrent uploads of 1 part + integrity verification. One of the things I'm thinking about is whether to try to retrofit whatever the standard becomes into our S3 endpoint or put it somewhere else. It probably makes the most sense for public buckets (aka static website hosting) but that's a whole other ball of wax (security, etc).

To defend multipart as well, the other piece it does really well is the ability to track the state of the upload which regular "PUT /path/to/file" doesn't. Downloads can only publish the object at the path once they complete so then how do you identify ongoing concurrent uploads to the same path? An overwrite upload can be thought of as a strongly consistent "delete + publish" step.

> List is unnecessarily flexible. Why should users have the ability to use any character as path delimiter. `/` should be fixed. List operation should use ordinary REST conventions, like `GET /dir/?from=token` should return JSON with items inside `dir` and continuation token.

Eh. In terms of complexity, the '/' delimiter really doesn't add any complexity to the API and "users" here are application developers, not end users. It adds a bunch of horrible implementation complexity and it took us a while to get this piece correct, but the complexity was having any delimiter, not around letting the user pick a custom delimiter. And having a delimiter is definitely helpful because people like to view folders. Would I wish that they simplified and require the delimiter to be 1 ASCII character instead of supporting arbitrary UTF-8 strings? Sure, but only from a test coverage perspective.

My observation is that a lot of the complexity from the APIs stems from two things. The first is that REST is a terrible way for accessing and mutating a strongly consistent data store. 16 years ago it made sense though and even today if you want interop with the browser, it's all you got. The second is that the more general problem of figuring out a sane API for a distributed strongly consistent database is an unsolved problem. HTTP has stood the test of time on that front but I wonder if something like cap'n'proto might be a better fit.

[1] https://developers.cloudflare.com/r2/examples/demo-worker/


I think I messaged by point about buckets poorly. All I want to say is that protocol does not need to know about buckets. For programmer consuming this service, all he needs to know is domain and path. Now for service provider, they can invent whatever domain schemes they want. bucket.service.com, bucket.region.service.com or just http://minio/ for simple use-case. And programmer would store that domain in his config file and that's about it.

That's avoiding the question of API to manage buckets. I never needed it and I think that this API should be outside of object storage API scope. Of course there should be some proprietary API on provider side, like they have API for everything. Or, in simple cases, no buckets at all, just some simple binary to serve a directory.


In my current app, we use cloudfront to serve browser requests. So it’s cloudfront that talks to S3 and deals with authentication, buckets etc. The requests from the web browser don’t know about the bucket.

S3 is designed for API access, not as a website.


What's the replication under R2? It's not specified anywhere.


> 1. Buckets is unnecessary concept. Why use it at all, when we have domain and path. That should be enough.

Bucket is a very important concept in S3 buckets. Because a S3 bucket is a container filled with S3 objects. A S3 object can have a fine-grained lifecycle policy defined and attached to them or can have one, all or a combination of the following:

  1. S3 objects can be versioned. S3 object versioning is very useful is many scenarios, and the S3 bucket can automatically purge expired object versions for the user after a desired period of time with zero effort – it is defined in the object lifecycle policy.
  2. S3 buckets are event-driven. A S3 bucket can be configured to emit an event when something happens to a S3 object in the bucket. There is no need to poll a S3 bucket for changes, for it can notify interested consumers via a new event. S3 buckets offer a rich set of events to choose from that cater for multiple use cases. S3 bucket events are a boon that also greatly simplifies the solution architecture, design and implementation.
  3. S3 objects can be locked in governance or in compliance modes for a certain amount of time. Both are useful to prevent the accidental data loss or preclude nefarious attempts to corrupt/delete the important data. The compliance object lock is very useful to satisfy statutory compliance requirements when sharing the data with regulatory and government bodies. Object lock is applied at the S3 object version level and can also be lifted automatically via a defined lifecycle policy wth zero coding effort required.
  4. S3 bucket can be set up to automatically replicate the data across multiple S3 buckets, either across different accounts or across regions. S3 buckets can be replicated in full or in part with zero extra coding required – replication rules are defined in the replication configuration.
  5. S3 buckets also allow one to choose a storage class which is equivalent to the automatic data archival.
> 2. Authentication is too convoluted. Signings, etc. Simple `Authorization` header should be enough.

S3 buckets are often useful to share the data with external business partners, therefore the identity of the party accessing accessing the bucket is to be asserted first. Multiple business partners can be granted access to the same bucket either in its entirety, or the S3 bucket can be partitioned and the partner can only be granted access to that partition. Therefore proper authentication is important.

> Basically I want to be able to use object storage with simple `fetch` and few lines of code. Without all that SDK nonsense.

Then you are probably do not need a S3 bucket and can stand up your own HTTP server with a block storage attached to it or explore alternative solutions.

S3 is a complete, feature rich software product that may or may not be suitable for one's needs. In most cases, though, it is very simple and easy to use and having to use the SDK is a non-existing problem.


I’ve only ever used MinIO as a stub for S3 in development, as it seems wrong/slow to require AWS access to start up a development environment. I like that it “Just Works” for this use-case, even though that’s definitely not where they’re getting their money from.

I can never see myself using it in production unless my workplace had a no cloud policy - but I guess there’s enough companies out there with that issue that they exist.

Docker pulls is a strange metric because of these two disparate use cases. MinIO success as a company depends on the latter but pulls would be strongly correlated with the former use case.


> Those pulls don’t even account for other hubs (Quay.io for example) or private repositories, the real number is likely far higher but we count what we can measure directly.

Does the final clause of this sentence support or distract from their achievement? I love Minio; I’m definitely contributing to those pulls. But that phrasing at the end puts a bad taste in my mouth—seems pretentious. Does anyone else feel that way?


I think it's a fair addition, what makes you think that?


I wonder how many of those are violating the AGPL license


Are you implying that merely using Minio is enough to force people to provide sources of their own services? If so, you need to be disabused of this misconception.

To be violating the AGPL License, someone would have to be providing a customized version of Minio to end users and not providing the source.


I think technically they also have to provide the source if they don't modify it. But that could probably just be having a link to the GitHub repo.


This might be a bit pedantic, but isn't it not necessarily that they provide a modified minio to users, but rather (or additionally) they are running a service that uses a modified minio under the hood?


No. The AGPL only consider distribution if the modified service is directly accessible to the users.

If someone makes modifications to Minio but the modifications are only for their internal services - or even if those accessing this Modified-Minio are internal to them - then it does not count as software distribution and therefore they can keep the source private without needing to provide their code modifications.

To take a few examples:

- if I use Minio to store data for my application and I make some improvements (e.g: SSO login for the console for back-office users), it won't count as distribution.

- If I have a set of services that use object storage running on my Minio cluster, and I add torrent-gateway capability to this cluster to make my applications able to find the data by content addressing, it won't count as network distribution and I do not need to share the source.

- If I start offering S3-compatible services to end users based on Minio, and if I start offering to people the ability to download files via a torrent gateway, I need to provide the source of my changes.

- If I make changes on the minio console to allow, e.g, a sleek file browsing UI with search, I need to provide the source with all my changes if this is acessible by the users.


Yes. I like to think of the AGPL as modifying the distribution part of the GPL to include direct network access by users. The "viral" part is the same between the two and is still linking code into a single program. AGPL libraries aren't very common, so it doesn't come up often.


Wow I had no idea that was the case, thanks for the detailed examples!


I used MinIO exactly once, and overall it was a joy to use. Happy to hear it's getting some amount of traction.


is anyone running a large baremetal distributed minio cluster in production? would love to know how reliable it is


Does minio have cluster support? I've always thought of it as non-clustered software.


Indeed it does. As for stability, I had a few decently sized customers running minio clusters in production and they never had any issues with it.

https://min.io/docs/minio/linux/operations/install-deploy-ma...


Nice to hear, I was planning to give it a shot.


It does, and it works (docs here: https://min.io/docs/minio/linux/operations/install-deploy-ma...) - but I'm not running a "large" cluster.


This is a weird metric, but sadly one of the only ways of measuring OSS usage.


Um, sorry that was me when I left a script running in an infinite loop.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: