Skip to main content

Docker Hub's 2026 Pricing Squeeze: What Self-Hosting a Registry Cache Actually Saves Your CI Pipeline

11 min readDora NodaDora Noda
Share
On this page

Docker Hub's 2026 free tier fits in three numbers: 10 pulls per hour if you're unauthenticated, 40 pulls per hour on a free Docker Personal account, and an authenticated overage model that can multiply a team's bill by roughly 10x the moment CI traffic spills past plan limits — all layered on a base per-user price that has climbed about 50% since 2024. For a laptop, none of this matters. For a CI fleet pulling base images hundreds of times a day from behind one NAT gateway, it's a tax on every green build.

Here's the punchline up front, for a representative 8-engineer team whose CI runs about 60 pipelines a day (roughly 300 image pulls daily, with afternoon bursts of ~50 pulls in a single hour):

SetupMonthly costBurst behavior
Unauthenticated pulls (10/hour per IP)$0Collapses in the first pipeline run
Docker Personal tokens (40/hour)$0Queues and retries every busy hour
Docker Team seats + overage~$160 base, $200–300 in spiky monthsWorks, but every pull is metered
Self-hosted pull-through cache (Hetzner)~€6–12 (or ~€5 marginal on an existing fleet)>95% of pulls never leave your network

That last row isn't a full Harbor deployment with replication and a database to babysit. It's a single stateless container running the CNCF Distribution registry in proxy mode — an afternoon of setup. The rest of this post shows the math line by line, where the crossover actually sits, and what owning the cache costs you in exchange.

How Docker Hub Got Here: 2024 → 2026

Docker Hub's squeeze didn't arrive in one announcement. It ratcheted.

November 2024 brought the repricing. Docker Pro went from $5 to $9 per month (annual), Team from $9 to $15 per user (monthly prices landed at $11 and $16), with Business holding at $24. That's a 45–80% jump depending on tier — the "roughly 50% base-price hike" every later change compounds on. The same announcement scheduled consumption-based pricing for Hub pulls and storage starting March 1, 2025, with published limits of 10 pulls/hour for unauthenticated users and 40 pulls/hour for Docker Personal.

February 2025 brought the walk-back. After sustained community pushback, Docker canceled per-pull consumption charges outright, kept softer interim limits (100 pulls per 6 hours unauthenticated, 200 per 6 hours for Personal), made pulls unlimited-with-fair-use for all paid plans from April 1, 2025, and indefinitely delayed storage billing — promising six months' notice before any future enforcement. The key phrase in that post was easy to skim past: the interim limits would hold "as we continue to evaluate developer feedback and identify appropriate long-term limits."

2026 is where the long-term limits landed, and they look like the original March 2025 plan: the free tier enforces the hourly windows (10/hour unauthenticated, 40/hour Personal), and paid plans' "unlimited with fair use" hardened into plan allowances with authenticated overage billing that prices excess pulls at roughly 10x the effective in-plan rate. Docker gave its promised notice. The direction of travel was never ambiguous — the 2025 reversal softened the schedule, not the destination.

Two mechanical details make these limits bite harder than the raw numbers suggest. A "pull" counts the manifest fetch plus download, and a multi-architecture image counts one pull per architecture — a single docker pull on a mixed amd64/arm64 fleet burns budget twice. And unauthenticated limits apply per IPv4 address, which means per NAT gateway, not per machine.

The Worked Example: One CI Pipeline's Pull Ledger

Take a team that looks like a lot of teams: 8 engineers, a monorepo with three services, CI on self-managed runners (GitHub Actions runners, GitLab runners, or in-cluster builders — the math is identical). The pipeline builds and tests on every push to a PR branch.

The volume. Around 60 pipeline runs on a normal weekday. Each run pulls roughly 5 images: a node:22-slim or golang:1.24 build base, a runtime base, plus postgres:17, redis, and a browser image for integration tests. That's ~300 pulls/day, call it 9,000 pulls a month — squarely the "hundreds of times a day" profile. Runner nodes are ephemeral or regularly pruned, so image caches on the runners themselves don't survive; that's the whole reason CI hits the registry so much harder than production nodes do.

The burst. Pulls aren't uniform. Merge trains and afternoon pushes mean 10 pipeline runs in flight at once is routine — ~50 pulls landing inside a single hour, all egressing through one NAT gateway with one public IP.

Now price that traffic three ways:

Unauthenticated: $0 and unusable. At 10 pulls/hour per IP, one two-service pipeline run exhausts the entire hour's budget for every runner behind the NAT. The eleventh pull returns 429 Too Many Requests, and every subsequent run that hour fails or retries into the same wall. This isn't a cost line; it's an outage.

Docker Personal tokens: $0 and flaky. Authenticate the runners with a free account's token and you get 40 pulls/hour — eight pipeline runs. The ninth queues behind backoff-and-retry logic you now have to write and tune. On a busy afternoon the queue compounds: builds that took 6 minutes take 15, engineers start rerunning "stuck" jobs (more pulls), and the failure mode is at its worst exactly when the team is shipping hardest. You can shard across multiple free accounts, but you've now built a token-rotation system to arbitrage a rate limit — fragile, and squarely against terms of service.

Docker Team: works, at metered prices. The supported answer is paid seats: 8 engineers plus 2 service accounts for CI is 10 seats × $16 = $160/month base. In-plan pulls flow freely — until they don't. Overage months are exactly the months you can't predict: a base-image CVE forces cache-busting rebuilds across every branch, a dependency upgrade invalidates every build layer, a release week doubles pipeline volume. With overage priced at roughly 10x the effective in-plan rate, those months land the bill in the $200–300 range — and the invoice arrives after the traffic, not before. The real cost isn't $160; it's $160 plus a variance term you don't control, plus the standing risk that next year's repricing ratchets again.

Annualized: $2,000–2,500 to rent access to images that are, overwhelmingly, the same few hundred megabytes of node, postgres, and golang layers pulled over and over.

The Self-Hosted Side: A Pull-Through Cache in an Afternoon

The counter-move is not "mirror all of Docker Hub" and it's not "run a full enterprise registry." It's a pull-through cache: a single container running the CNCF Distribution registry (the same code that used to be docker/distribution) in proxy mode. First request for a blob goes upstream and gets cached; every subsequent request is served from your own disk.

The entire load-bearing configuration is this:

yaml
# config.yml for registry:3 (CNCF Distribution)
proxy:
  remoteurl: https://registry-1.docker.io
  username: ci-bot        # optional; one authenticated identity upstream
  password: "<token>"
  ttl: 168h               # evict cached content after 7 days

Point every runner's Docker daemon at it:

json
// /etc/docker/daemon.json
{
  "registry-mirrors": ["https://mirror.internal.example"]
}

That's the whole trick. The daemon consults the mirror first and falls back to Docker Hub automatically if the cache is down — so the cache is not even a new single point of failure for pulls, just a pressure-relief valve in front of the meter.

What it does to the ledger. CI base images change on a weekly-to-monthly cadence; the same tags are pulled thousands of times between changes. After the first day of warmup, cache hit rates on CI traffic routinely exceed 95%. The team's 9,000 monthly pulls collapse to a few hundred upstream requests — mostly manifest freshness checks and the occasional new tag — spread across the month. That volume fits inside a single free authenticated account's 40/hour without ever queuing. The paid-seat question doesn't get answered; it gets deleted.

What it costs. The cache is a stateless container plus a disk. Sized for this team: 2 vCPU, 4 GB RAM, and a 100 GB volume is generous — that's one small Hetzner cloud box at €6–12/month even after 2026's repricings. If you already run your own fleet, it's a Deployment with a PersistentVolume: effectively ~€5/month of marginal disk on capacity you own. There's a bonus that shows up in every build log: pulls now come over the local network at NIC speed instead of the public internet, so the cache typically shortens the pipeline it's protecting — and CI keeps building through a Docker Hub outage as long as the tags it needs are warm.

When you want Harbor instead. A pull-through cache only pulls. If you also want to push your own images, scan them, enforce RBAC, and set retention policies, that's a real registry — Harbor is the CNCF-graduated default, and it wants a slightly bigger box (2–4 vCPU, ~8 GB, Postgres included) — call it ~€15/month on the same provider, plus genuine upgrade and backup responsibilities. Harbor can act as a proxy-cache project too, so some teams run just Harbor for both jobs. But don't let the bigger tool block the smaller win: the cache alone captures most of the savings and carries almost none of the operational weight.

Where the Crossover Sits — and Where the Hub Is Still Fine

The honest version of this comparison depends on one variable more than any other: pulls per hour at peak, per public IP. Sensitivity across the range:

ProfilePeak pulls/hourDocker Hub free tierVerdict
Solo dev, laptop + one VPS< 10Fits even unauthenticated, usuallyDo nothing
Side project with light CI10–40Fits with a free Personal tokenAuthenticate your runners; done
Small team, shared NAT, real CI40–100Queues at every burstCache pays for itself in month one
This post's team (8 eng, 60 runs/day)~50, spikyFails bursts; paid = $160–300/moCache saves ~$2,000/year
Multi-team org, many pipelines100s+Overage dominates the billCache + Harbor; this is infrastructure now

Two things this table refuses to hide. First, the free tier is genuinely fine below 40 pulls/hour — if your peak traffic authenticates and stays under the line, self-hosting a cache to save $0 is hobbyism, not engineering. Second, the trigger is rarely average volume; it's the NAT multiplier and burstiness. Twenty machines behind one IP share one unauthenticated budget, and CI is the burstiest workload most teams run. That's why registry pain almost always shows up first in CI logs, not on developer laptops.

And the ownership column has real entries too. Running the cache means owning disk garbage collection (set the ttl, monitor the volume, let the registry's scheduled GC reap stale blobs), TLS and auth for the mirror endpoint (mandatory the moment upstream credentials can reach private images), and one genuine architectural gotcha: the Docker daemon's registry-mirrors setting only mirrors Docker Hub. Traffic to ghcr.io or quay.io ignores it. Under containerd you configure per-registry mirrors instead, or you run one cache instance per upstream — the config is trivial, but it's config you own. Harbor adds database backups and a real upgrade cadence on top. None of this is heavy, but none of it is zero, and pretending otherwise is how "self-hosting saves money" posts earn their bad reputation.

The Meter Is the Message

The line-by-line answer: for a CI pipeline in the hundreds-of-pulls-a-day class, Docker Hub's 2026 model costs $160–300 a month with unbounded variance, and a self-hosted pull-through cache does the same job for under €12 a month with none — roughly a 95% reduction, achieved not by replacing Docker Hub but by refusing to pay per-request prices for bytes you've already downloaded. The rate-limit math stops being an annoyance and becomes a line item worth owning at almost exactly the point where CI runners share a NAT and bursts cross 40 pulls an hour.

The larger lesson generalizes past Docker. The 2024 announcement, the 2025 reversal, and the 2026 enforcement are one continuous story: a rented dependency's pricing is a variable you don't control, and every "unlimited, fair use" clause is a future meter with a grace period. Caches, mirrors, and self-hosted infrastructure are how you convert that variable back into a constant. A hundred gigabytes of disk you own is a remarkably cheap hedge against someone else's pricing committee.


Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. A registry cache like the one in this post is exactly the kind of thing a Cluster API-managed bex fleet runs as just another workload: one manifest, owned disks, no meter. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex