Docker Hub Rate Limits Are Coming – Here’s How to Outsmart Them with a private Registry
Avoid Docker Hub rate limits by using Harbor as a proxy cache. Learn how to install and configure Harbor, redirect image pulls in Kubernetes, Docker, Podman, and OpenShift, and reduce the costs of Docker Hub subscriptions with IDMS/ITMS, admission webhooks and CoreDNS rewrites.
With Docker Hub's new rate limits taking effect on March 1, 2025, organizations that rely on frequent image pulls could face disruption — or unexpected costs.
The new limits are as follows:
- Unauthenticated Users: 10 pulls per hour per IP address
- Authenticated Personal Users: 40 pulls per hour
- Pro Subscribers: 25,000 pulls per month
- Team Subscribers: 100,000 pulls per month
- Business Subscribers: 1,000,000 pulls per month
See Docker's official documentation for more information.
Instead of paying for higher tier subscriptions, a smarter approach is to use your own container registry, as a pull-through cache for Docker Hub. Private registries that you could use include Harbor, Spegel, Zot or the official CNCF Distribution Registry - the successor to the open source Docker Registry.
What to Expect
This article will walk you through installing and configuring Harbor, setting up container environments (Docker, containerd, Kubernetes, OpenShift, and more) to automatically redirect image pulls, and calculating potential cost savings over paid Docker Hub plans. Whether you're running a small DevOps team or a large enterprise, this guide will help you optimize your container workflows while avoiding rate caps — without breaking the bank! 🚀
Setting Up Harbor as a Docker Hub Mirror
Harbor provides a robust and secure environment for hosting and managing container images. By configuring Harbor to mirror Docker Hub, you can cache frequently used images locally, minimizing the impact of Docker Hub's rate limits.
Prerequisites
Ensure you have:
- A running Kubernetes cluster (v1.20+)
- Helm 3 installed (
helm version
) - kubectl configured to interact with the cluster
- An Ingress controller (e.g., Traefik, NGINX)
Install Harbor on Kubernetes
This is not a deep dive into the various options provided by the Harbor Helm Chart. The goal here is to get an instance up and running. See the documentation on ArtifactHub for more details.
helm repo add harbor https://helm.goharbor.io
helm repo update
helm install my-harbor harbor/harbor --set expose.ingress.hosts.core=example.com --set externalURL=https://example.com --set harborAdminPassword=SuperSecurePassword
Configure Harbor as Pull-Through Cache
Go to example.com
and log in to your Harbor instance as an administrator using admin=SuperSecurePassword
credentials.
- Add an Registry Endpoint: https://goharbor.io/docs/2.4.0/administration/configuring-replication/create-replication-endpoints
- Administration → Registries → New Endpoint → Provider = Docker Hub
- Add a Proxy Cache Project: https://goharbor.io/docs/2.4.0/administration/configure-proxy-cache
- Projects → New Project → Enable Proxy Cache → Select the Docker-Hub Registry Endpoint from the list
Configuring Container Platforms to Use Harbor
After setting up Harbor as a mirror, configure your container runtime environments to use Harbor for image pulls. This setup ensures that requests for images available in Harbor are served locally, bypassing Docker Hub and its associated rate limits.
containerd
Customize the config.toml
file, usually located in /etc/containerd/config.toml
:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://<harbor_hostname>/proxy_cache_project_name"]
Apply the changes by restarting the containerd
service:
sudo systemctl restart containerd
Docker
You can configure Docker to use Harbor as a registry mirror by customizing the daemon.json
, typically located at /etc/docker/daemon.json
:
{
"registry-mirrors": ["https://<harbor_hostname>/<proxy_cache_project_name>"]
}
Apply the changes by restarting the docker
service:
sudo systemctl restart docker
Podman
Add the following configuration to your registries.conf
configuration file, usually located in $HOME/.config/containers/registries.conf
or /etc/containers/registries.conf
:
[[registry]]
prefix = "docker.io"
location = "<harbor_hostname>/<proxy_cache_project_name>"
Kubernetes
There is no straightforward approach that works at the Kubernetes level. You can, of course, adjust the configuration of the underlying Container Runtime, such as containerd, to achieve automatic rewriting of docker.io to your own private registry. But there are two other solutions that might work for your setup.
You could use a Mutating Admission Webhook - like Kyverno - that changes the url of the image in your resource definitions (Deployments, StatefulSets, DaemonSets, ...). However, there are some drawbacks: It only works on newly created resources, you have to add every image and tag you want to rewrite, and you will experience some minor issues when using GitOps tools like Argo CD (which can still solved). A potential ClusterPolicy for Kyverno might look like this:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: rewrite-dockerhub-images
spec:
rules:
- name: rewrite-image-to-harbor
match:
resources:
kinds:
- Pod
mutate:
patchesJson6902: |-
- op: replace
path: "/spec/containers/0/image"
value: "<harbor_hostname>/<proxy_cache_project_name>/library/nginx:latest"
Another approach would be to instruct the DNS server bundled with Kubernetes - CoreDNS - to rewrite every request to docker.io to your private registry. This can be done using the ConfigMap coredns
from the kube-system
namespace:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
rewrite name docker.io <harbor_domain>
forward . /etc/resolv.conf
}
OpenShift
OpenShift offers a better way to solve our problem: resource types called ImageDigestMirrorSet
and ImageTagMirrorSet
. Both can be used to automatically rewrite image requests from one registry to another one:
apiVersion: operator.openshift.io/v1alpha1
kind: ImageDigestMirrorSet
metadata:
name: use-harbor
spec:
imageDigestMirrors:
- source: docker.io
mirrors:
- <harbor_hostname>/<proxy_cache_project_name>
Potential Cost Savings: A Real-World Comparison
Let’s consider an organization running a 100-node Kubernetes cluster, with each node pulling images multiple times per day.
- Without Harbor (paid Docker Hub Subscription):
- Assume each node pulls 500 images per day
- That’s 50,000 pulls per day for the entire cluster
- Docker Hub’s Business plan is $300/month and allows 50,000 pulls per day for a total of $3,600/year
- With Harbor (Self-Hosted, no paid Docker Hub Subscription):
- Only the first pull of an image hits Docker Hub
- All subsequent pulls come from Harbor’s local cache
- External pulls are reduced to a fraction of previous usage
- No need for a Docker Hub Business plan
- Cost is only Harbor storage & maintenance, typically much lower than $300/month
Conclusion
By setting up a private Registry - that supports a Proxy Cache - as a Docker Hub proxy cache and redirecting container platforms to use it, organizations can bypass rate limits, improve deployment speeds, and save money. With the rising cost of cloud services, this setup is a smart investment that balances performance and cost efficiency.
And hey, if Harbor saves you enough money, maybe you can finally get that fancy coffee machine for the office. ☕