A merge queue on a Cluster API-managed fleet kicks off twelve builds in the same five minutes. Each one pulls a base image, a builder stage, and a handful of dependency layers — call it eight pulls per build. That's 96 pulls before lunch, from one shared IP, and Docker Hub's answer is 429 Too Many Requests: toomanyrequests: You have reached your pull rate limit.
The surprising part isn't that the limit exists — it's that in 2026, after years of Docker publicly wrestling with this policy, the number is still exactly what it was when the current rules took effect in April 2025: 100 pulls per 6 hours for unauthenticated requests, 200 pulls per 6 hours for a free Docker Personal account, and unlimited for anyone on a paid plan. Docker Hub confirmed in its own policy update that these are the numbers "that will remain in place" as it keeps evaluating feedback — and, notably, it also cancelled its once-floated per-pull metering entirely, committing to at least six months' notice before changing enforcement again. There's no hidden overage bill waiting to surprise you; the limit is just a hard wall.
The Numbers, Plainly
| Account type | Limit | Enforced by |
|---|---|---|
| Unauthenticated | 100 pulls / 6 hours | Source IPv4 address or IPv6 /64 subnet |
| Docker Personal (free, authenticated) | 200 pulls / 6 hours | Account, not IP |
| Docker Pro / Team / Business | Unlimited pulls, no overage charge | Account (fair-use only) |
Docker Pro runs $9/user/month billed annually ($11 month-to-month); Team and Business scale from there per seat. That's the entire menu: free-and-capped, or paid-and-uncapped. There is no metered middle tier where you pay per pull — Docker looked at that model and walked away from it.
The part that trips teams up is the enforcement unit. Unauthenticated limits are per-IP, which means every CI runner, build node, or Kubernetes worker sharing one NAT gateway or one Cluster API-provisioned subnet shares the same 100-pull budget. Authenticate, and the limit becomes per-account instead of per-IP — which fixes the "whole office shares one IP" problem but creates a new one: every machine using that same Docker Hub credential now draws from one 200-pull pool, so a fleet of build nodes all logged in as the same service account is really just a bigger, better-labeled version of the IP problem.
Why "Pulls Per Day" Is the Wrong Way to Think About It
The instinct is to do daily-average math: 200 pulls/6hr times four windows is 800/day, so a team running "a few dozen" builds surely has headroom. That math is wrong for CI/CD workloads specifically, because builds don't arrive as a smooth trickle — they arrive as bursts.
Take a self-hosted PaaS control plane that redeploys tenant services on every merge and rolls a base-image update across a Cluster API node pool a few times a week. A single burst — a merge train clearing five queued PRs, or a platform-wide image bump triggering rebuilds across twenty tenant apps — can look like this:
- 15 concurrent builds
- 7 layers pulled per build (base OS layer, language runtime layer, 3-4 dependency layers, final stage)
- 105 pulls in under ten minutes
That single burst alone exceeds the unauthenticated 100/6hr limit and eats more than half the free-account 200/6hr limit — and it happened once, not over a whole day. A fleet that "only" triggers a few dozen builds daily can still blow through the free tier by mid-morning if those builds cluster around a merge queue, a scheduled redeploy, or a coordinated base-image bump, which is exactly the shape of workload a Cluster API-managed fleet produces by design. The relevant number isn't your daily build count — it's how many builds can land in the same 10-15 minute window, multiplied by layers per build.
Three Off-Ramps, and What Each One Actually Buys
Once a fleet crosses that burst threshold, there are three concrete paths — not one "just add a cache" answer — and each has a different bill and a different failure mode.
1. Authenticate (free, but fragile). Logging every build node in with one Docker Hub account moves the limit from 100/6hr per-IP to 200/6hr per-account. It costs nothing and takes minutes, but it's a stopgap: you've traded a shared-IP bottleneck for a shared-credential bottleneck, and a single leaked or rotated token now affects every node at once.
2. Pay per seat (Docker Pro/Team). At $9-11/user/month, this removes the pull ceiling entirely with zero infrastructure to run. The catch is what it scales with: cost tracks headcount, not build volume. A five-person team on Docker Team is paying a flat monthly fee regardless of whether they run ten builds a day or ten thousand — and you're still fully dependent on Docker Hub's uptime and policy decisions, which is the exact dependency a self-hosted platform is usually trying to shed everywhere else in its stack.
3. Pull-through cache (Harbor proxy cache or Zot, self-hosted). This is the option that actually changes the shape of the problem instead of just paying around it. A pull-through cache sits between your fleet and Docker Hub: the first pull of node:22-slim hits Docker Hub and gets cached locally; every subsequent pull of that same image and tag, from any build node, is served from the cache and never touches Docker Hub's rate limit at all. Google's mirror.gcr.io works the same way for GKE by default, and the mechanism is identical whether you run it yourself or borrow a cloud vendor's version: your fleet's 15-builds-in-ten-minutes burst becomes, at most, one authenticated upstream pull per unique image:tag combination — because every base and builder image in a CI pipeline is reused across dozens of builds, cache hit rates on the second build onward are effectively total.
The tradeoff: you're now running a service, and it needs disk, an eviction/garbage-collection policy, and monitoring like anything else in the cluster. But it converts a per-seat, per-headcount cost into a fixed, one-time infrastructure cost that scales with unique images, not with pull volume or team size.
When a Full Self-Hosted Registry — Not Just a Cache — Pays Off
A pull-through cache alone solves the Docker Hub problem. A full self-hosted registry solves it and something else you probably already need: somewhere to store the images your own platform builds for tenants.
That reframes the decision. If your Cluster API-managed PaaS is already building and pushing per-tenant container images — which any git-push-to-running-service platform is — you need a private registry regardless of Docker Hub rate limits. Once that registry exists, adding a proxy-cache rule for Docker Hub's public images is a config change, not a new service. The marginal cost of also caching docker.io through the registry you're already running and already paying to operate is close to zero.
The choice between Harbor and Zot comes down to how much you want that registry to also be an admin surface:
- Harbor ships a full UI, RBAC, vulnerability scanning, and project-level quotas — but it requires PostgreSQL and Redis as hard dependencies, and CNCF's own production guidance puts a comfortable baseline at 4 CPU cores, 8GB RAM, and 750GB of disk for a first deployment. That's a real service to operate, not a sidecar.
- Zot is a single Go binary with no external database dependency, OCI 1.1-compliant, with content-addressable deduplication that cuts storage 30-50% versus a naive registry. It skips the admin UI and RBAC layer Harbor offers, but for a platform whose control plane is already the source of truth for tenant permissions, that's often not a feature you needed the registry to duplicate.
For a platform that already runs its own multi-tenant control plane and just needs image storage plus a Docker Hub proxy cache, Zot's smaller footprint tends to win — one more Postgres instance to keep patched and backed up isn't free, even when the registry itself is. For a platform that wants the registry to be a standalone, browsable, RBAC'd artifact store independent of the rest of the stack, Harbor's heavier footprint buys real functionality.
The Concrete Threshold
Put the numbers together and the decision rule is simple: if your fleet can ever produce more than roughly 15 concurrent or near-simultaneous builds — a merge train, a scheduled redeploy, a base-image bump across tenants — you will cross the free 200/6hr ceiling in a single burst, regardless of how modest your daily total looks on a dashboard. At that point, authenticating buys you a few days; paying per seat buys you an unlimited ceiling that costs more as your team grows rather than as your fleet grows; and a pull-through cache is the only option that scales with unique images instead of headcount or pull count. If you're already running (or need to run) a private registry for your own tenant images, extending it into a Docker Hub proxy cache is close to free — which is exactly the workload shape a Cluster API-managed fleet building services on machines it owns, dozens of times a day, was always going to hit.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with a Cluster API-managed fleet that owns its own image pipeline end to end. Star the repo on GitHub or deploy your first app today.



