Docker Hub Rate Limits: die Zahl in der Doku stimmt nicht
14.08.2026
Thema: alle Beiträge zu Docker
Wie hoch die Docker-Hub-Limits wirklich sind, wie du sie selbst misst und warum ein Pull-through-Spiegel im Ernstfall weniger rettet, als alle annehmen.
Inhaltsverzeichnis
- Die Antwort vorweg
- Miss dein Limit selbst
- Was bei einem Ausfall passiert
- Der Pull-through-Spiegel
- Der Härtetest: Spiegel im Ausfall
- Was wirklich hilft
- Darf man das überhaupt spiegeln?
- Wie global ist der einzelne Punkt?
- Was das mit Souveränität zu tun hat
- Fazit
- Belege
Du planst mit einer Zahl, die du nicht kennst.
Das klingt hart, ist aber der nüchterne Befund nach einem Nachmittag Messen. Die Dokumentation von Docker Hub nennt ein Limit, die Registry selbst meldet ein anderes, und die Antwort auf die eigentlich wichtige Frage, was bei einem Ausfall passiert, steht in keinem der beiden.
Fangen wir mit dem an, was messbar ist.
Die Antwort vorweg
Stand 1. August 2026:
| Zugang | Limit laut Dokumentation | Limit laut HTTP-Header |
|---|---|---|
| anonym | 100 Pulls / 6 Stunden | 100 Pulls / 1 Stunde |
| Docker Personal, angemeldet | 200 Pulls / 6 Stunden | nicht gemessen |
| Pro, Team, Business | unbegrenzt (fair use) | entfällt |
Anonym zählt Docker pro IPv4-Adresse oder pro IPv6-/64-Netz, nicht pro Rechner. Ein Büro hinter einer NAT-Adresse ist also ein einziger Zähler, ebenso ein Kubernetes-Knotenpool hinter einem NAT-Gateway. Angemeldet zählt Docker pro Konto.
Die Zahlen für die bezahlten Stufen und die 6-Stunden-Angabe stammen von der Dokumentationsseite zu den Pull-Limits. Den Widerspruch in der ersten Zeile erkläre ich gleich.
Ein Hinweis vorweg, der in vielen Anleitungen fehlt: Nimm keine Zahl aus einem Blogbeitrag, auch nicht aus diesem. Docker hat die Limits mehrfach geändert und einmal eine angekündigte Änderung zurückgezogen. Zum 1. April 2025 sollten 10 Pulls pro Stunde anonym gelten, umgesetzt wurde es nicht. Miss selbst, und zwar so:
Miss dein Limit selbst
Zwei Kommandos, keine Installation außer curl und jq.
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -s -I -H "Authorization: Bearer $TOKEN" \
https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest \
| grep -i ratelimit
Die Antwort:
ratelimit-limit: 100;w=3600
ratelimit-remaining: 96;w=3600
w ist das Zeitfenster in Sekunden. 3600, also eine Stunde. Die Beispielausgabe in Dockers eigener Dokumentation zeigt an derselben Stelle w=21600, also sechs Stunden, und der Fließtext daneben nennt ebenfalls sechs Stunden.
Ich kann nicht auflösen, welcher Wert durchgesetzt wird. Möglich ist, dass Docker auf ein Stundenfenster umgestellt hat und die Doku hinterherhinkt, ebenso möglich ist ein statischer Wert im Header. Beides wäre für dich unangenehm, weil du in beiden Fällen mit einer Zahl planst, die du nicht überprüfen kannst.
Praktisch heißt das: Plane mit der kleineren Zahl. 100 Pulls pro sechs Stunden ist die konservative Annahme, und wenn es in Wahrheit 100 pro Stunde sind, hast du Luft statt eines Ausfalls.
Der Fallstrick mit HEAD
Wenn du im Beispiel oben -I durch ein echtes GET ersetzt, sinkt ratelimit-remaining. Mit -I nicht. Das steht so auch in der Doku:
Using GET emulates a real pull and counts towards the limit. Using HEAD won’t.
Nützlich zum Nachsehen, gefährlich beim Nachbauen. Ein Monitoring, das dein Restkontingent per GET abfragt, verbraucht es dabei. Nimm HEAD.
Noch ein Detail aus der Messung: Der Zähler ist nicht sofort konsistent. Drei aufeinanderfolgende GET ergaben 96, 95, 95. Zum Alarmieren an einer scharfen Schwelle taugt er deshalb nicht, zum Erkennen eines Trends schon.
Was der Header sonst noch verrät
Die Antwort enthält einen Header docker-ratelimit-source mit der IP-Adresse, unter der Docker dich zählt. Der ist beim Debuggen von NAT-Problemen Gold wert, denn er zeigt dir, ob dein Cluster wirklich unter einer einzigen Adresse herauskommt.
Er gehört aus demselben Grund nicht in ein öffentliches Ticket, Issue oder Repository. Es ist deine öffentliche IP.
Was bei einem Ausfall passiert
Das Limit ist der Alltagsfall. Der Ausfall ist der teure.
Am 20. Oktober 2025 fiel die AWS-Region us-east-1 aus, und mit ihr Docker Hub. Aus Dockers eigenem Bericht: Ab 06:48 UTC stiegen die Fehlerraten, ab 08:01 UTC waren alle Dienste nicht verfügbar, betroffen waren Hub, Scout, Build Cloud, Hardened Images, Automated Builds und Testcontainers Cloud. Die vollständige Wiederherstellung meldet Docker für den Folgetag, 21. Oktober um 09:42 UTC.
Docker nennt in dem Bericht selbst zwei Konsequenzen: Cache-Schichten ausbauen und Leseoperationen regional redundant machen, beginnend mit Image-Pulls. Beides ist noch nicht da.
Der Standardrat lautet an dieser Stelle: bau dir einen Pull-through-Spiegel. Der Rat ist richtig. Er ist nur deutlich schwächer, als er klingt, und das lässt sich messen.
Der Pull-through-Spiegel
Ein Spiegel ist eine Registry, die Anfragen an den Hub weiterreicht und die Antwort behält. Beim zweiten Mal antwortet sie selbst. Die gesamte Konfiguration ist ein Abschnitt:
version: 0.1
storage:
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
proxy:
remoteurl: https://registry-1.docker.io
Starten:
docker run -d --name hubspiegel -p 5000:5000 \
-v "$PWD/config.yml:/etc/distribution/config.yml:ro" \
-v spiegeldaten:/var/lib/registry \
registry:3
Und auf den Clients in /etc/docker/daemon.json:
{
"registry-mirrors": ["http://hubspiegel:5000"]
}
Danach zieht jeder Pull über den Spiegel, und der Hub sieht einen Abruf statt hundert. Für das Limit ist das die richtige und ausreichende Antwort.
Zwei Einschränkungen, die in der Doku stehen und gern übersehen werden. Erstens: Nur Docker Hub lässt sich so spiegeln, keine andere Registry. Zweitens: Wenn du im proxy-Abschnitt Zugangsdaten hinterlegst, sind alle privaten Images dieses Kontos über deinen Spiegel erreichbar. Ein Spiegel mit Zugangsdaten braucht deshalb zwingend eine eigene Zugangskontrolle.
Der Härtetest: Spiegel im Ausfall
Jetzt die Frage, um die es eigentlich geht. Ich habe den Spiegel aufgewärmt, ihm dann die Außenanbindung genommen und gemessen. Nicht per DNS-Trick, weil Verbindungs-Pools den unterlaufen, sondern über ein zweites, bewusst internes Docker-Netz. Der komplette Aufbau steht am Ende des Beitrags.
Erstens, ein gespiegeltes Image während des Ausfalls. Es wird ausgeliefert. Aber:
alpine:3.22 -> HTTP 200
real 0m15.00s
Fünfzehn Sekunden, in drei Läufen jeweils exakt. Bei erreichbarem Hub sind es 0,3 bis 0,7 Sekunden. Der Spiegel fragt zuerst oben nach und liefert erst aus, wenn diese Anfrage in den Timeout gelaufen ist. Ein Pod mit sechs Containern startet damit statt in Sekunden in eineinhalb Minuten. Liveness-Proben und Deployment-Timeouts sind auf solche Zahlen selten eingestellt.
Zweitens, ein Image, das nicht im Cache liegt.
busybox:1.37 -> HTTP 404
real 0m15.00s
Wichtiger als der Fehler ist sein Wortlaut. Im Log der Registry steht:
err.code="manifest unknown" err.detail="unknown tag=1.37"
Ein Hub-Ausfall sieht für den Aufrufer aus wie ein Tippfehler im Tag. Wer diesen Fehler um drei Uhr nachts bekommt, sucht zuerst im eigenen Manifest.
Drittens, und das ist der eigentliche Befund: der Spiegel startet während des Ausfalls neu.
panic: Get "https://registry-1.docker.io/v2/": dial tcp: connect: connection refused
Container-Status: Exited (2)
Der Spiegel prüft beim Start die Verbindung nach oben und beendet sich, wenn sie fehlschlägt. Der gefüllte Cache liegt auf der Platte, der Prozess kommt nur nie so weit, ihn zu öffnen.
Genau dieser Fall ist der wahrscheinlichste. Ein Ausfall dauert Stunden, und in Stunden passiert in einem Cluster einiges: ein Knoten wird neu gestartet, der Autoscaler räumt auf, ein Node-Pool wird ersetzt, jemand rollt ein Update aus. Der Spiegel, den du gegen den Ausfall gebaut hast, überlebt den Ausfall nicht.
| Lage | Ergebnis |
|---|---|
| Spiegel läuft, Image im Cache | funktioniert, 15 s statt 0,5 s |
| Spiegel läuft, Image nicht im Cache | 404 nach 15 s, irreführende Meldung |
| Spiegel startet neu | Absturz, kommt nicht hoch |
Gemessen mit registry:3 in Version 3.1.1, Digest sha256:1be55279….
Was wirklich hilft
Der Spiegel senkt die Abhängigkeit. Beseitigen tut sie erst etwas anderes.
Eine echte Kopie statt eines Proxys. Der Unterschied ist der ganze Punkt: Ein Proxy fragt oben nach, eine Registry mit kopierten Images nicht. skopeo copy oder oras cp in einem geplanten Lauf, Zielregistry ist deine eigene, und die Referenzen in deinen Manifesten zeigen dorthin. Das ist mehr Arbeit als ein proxy-Abschnitt und der einzige Weg, der im Ausfall trägt.
Digests statt Tags. alpine:3.22 ist eine Bitte, alpine@sha256:1435… ist eine Adresse. Ein Digest ist inhaltsadressiert und damit unabhängig davon, wer ihn ausliefert. Nebenbei löst das ein zweites Problem, denn ein Tag kann sich unter dir ändern.
imagePullPolicy: IfNotPresent. Klingt banal, ist im Ausfall der Unterschied zwischen einem Pod, der startet, und einem, der es nicht tut. Mit Always fragt Kubelet bei jedem Start nach, auch wenn das Image auf dem Knoten liegt. Vorsicht: Bei Tag latest setzt Kubernetes stillschweigend Always, was ein weiteres Argument für Digests ist.
Anmelden, auch ohne bezahlten Tarif. Ein docker login mit einem kostenlosen Konto verdoppelt das Kontingent und, wichtiger, löst es von der IP-Adresse. In CI ist das ein Secret und eine Zeile.
Den Rest-Zähler überwachen. Der Header oben, per HEAD, als Metrik. Dann siehst du das Limit kommen, statt es zu erreichen.
Darf man das überhaupt spiegeln?
Diese Frage gehört gestellt, bevor man einen Spiegel empfiehlt. In den Nutzungsbedingungen steht in Abschnitt 4 ein Verbot, das Dienste zu nutzen, um
content for an unauthorized commercial service
zu spiegeln oder zu replizieren. Ein interner Spiegel für die eigene Pipeline ist kein kommerzieller Dienst für Dritte, und Docker dokumentiert den Pull-through-Cache selbst. Wer daraus ein Angebot für andere baut, bewegt sich in einem anderen Feld. Das ist keine Rechtsberatung, sondern der Hinweis, wo die Linie ungefähr liegt.
Der interessantere Satz steht ein paar Zeilen weiter, in Abschnitt 4A:
Docker reserves the right to modify, restrict, or discontinue Free Hub Access at any time with or without notice.
Im Blogbeitrag von 2025 verspricht Docker sechs Monate Vorlauf für künftige Änderungen. In den Bedingungen, die tatsächlich gelten, steht „mit oder ohne Ankündigung”. Wenn beides nebeneinander steht, gilt das Dokument, das unterschrieben wird.
Wie global ist der einzelne Punkt?
Bleibt die dritte Frage. Docker Hub ist nicht die einzige Registry, aber er ist der Vorgabewert. Ein FROM alpine in einem Dockerfile, ein image: nginx in einem Manifest, jedes Beispiel in jedem Tutorial: All das zeigt ohne weiteres Zutun auf Docker Hub. Der Standardwert ist die wirksamste Form von Marktanteil.
Der 20. Oktober 2025 hat das sichtbar gemacht. Ausgefallen ist eine AWS-Region. Betroffen waren Build-Pipelines auf allen Kontinenten und bei allen anderen Anbietern, weil sie alle an derselben Stelle ihre Basis-Images holen. Die Kette ist länger als gedacht: ein Rechenzentrum eines Cloud-Anbieters, darauf ein Registry-Dienst, darauf praktisch jeder Container-Build weltweit.
Und dann sind da noch die zweiten Ordnung. Wer auf GitHub Actions baut, mit einem Image von Docker Hub, und dabei einen Cache eines dritten Anbieters nutzt, hängt an drei fremden Diensten für einen Build, der lokal offline funktionieren würde.
Das ist keine Anklage. Ein zentraler Vorgabewert hat das Ökosystem überhaupt erst so schnell gemacht. Es ist eine Einladung, die eigene Abhängigkeit einmal aufzuschreiben, statt sie zu vermuten.
Was das mit Souveränität zu tun hat
Ein Rate Limit fühlt sich an wie ein technisches Problem. Es ist keines.
Die Zahl steht in keiner Spezifikation. Sie ist eine Geschäftsbedingung, einseitig änderbar, und dass sie 2025 einmal geändert und dann doch nicht durchgesetzt wurde, zeigt genau das. Die technische Seite ist trivial: Ein Pull ist eine Abbildung von einem Digest auf Bytes, und Bytes lassen sich kopieren. Nichts an einem Container-Image zwingt dich, es bei Docker zu holen.
Was dich dorthin zwingt, ist der Vorgabewert und die Bequemlichkeit. Beides ist reparierbar, und zwar bevor es weh tut, nicht danach.
Genau darum geht es hier immer: verstehen, was der Dienst technisch tut, und wissen, unter welchen Bedingungen du ihn nutzt. Das erste sagt dir, dass ein Spiegel möglich ist. Das zweite sagt dir, warum du ihn brauchst.
Fazit
- Miss dein Limit selbst, mit
HEADgegenratelimitpreview/test, und schreib das Datum dazu. Doku und Header widersprachen sich am 1. August 2026. - Anonym zählt Docker pro IP-Adresse. Ein
docker loginmit kostenlosem Konto verdoppelt das Kontingent und löst es von der NAT-Adresse. - Ein Pull-through-Spiegel löst das Limit-Problem vollständig und das Ausfall-Problem nur halb.
- Der Spiegel startet nicht, wenn der Hub beim Start nicht erreichbar ist. Wer sich auf ihn verlässt, sollte diesen Fall einmal selbst ausgelöst haben.
- Gegen einen Ausfall hilft nur eine echte Kopie in einer eigenen Registry, dazu Digests statt Tags und
imagePullPolicy: IfNotPresent.
Damit ist die Frage aufgemacht, die dieser Beitrag nicht mehr beantwortet: Wie sähe ein Proxy aus, der diese Schwächen nicht hat, und wie viel Code braucht der eigentlich? Im nächsten Teil, er erscheint am Freitag, dem 21. August, fällt die Antwort aus genau diesen Messungen heraus, und sie ist kleiner als gedacht.
Belege
Alle Messungen dieses Beitrags stammen vom 1. August 2026 und lassen sich in wenigen Minuten nachstellen. Die Limit-Abfrage steht oben unter „Miss dein Limit selbst”, die Spiegel-Konfiguration unter „Der Pull-through-Spiegel”. Fehlt nur der Aufbau der Ausfall-Simulation:
docker network create netz-aussen # mit Internet
docker network create --internal netz-innen # ohne Internet
docker volume create spiegeldaten
docker run -d --name hubspiegel --network netz-aussen \
-v "$PWD/config.yml:/etc/distribution/config.yml:ro" \
-v spiegeldaten:/var/lib/registry registry:3
docker network connect netz-innen hubspiegel
docker run -d --name klient --network netz-innen alpine:3.22 sleep 600
# Cache aufwaermen, solange der Hub erreichbar ist
docker exec klient wget -q -O /dev/null \
--header="Accept: application/vnd.oci.image.index.v1+json" \
http://hubspiegel:5000/v2/library/alpine/manifests/3.22
# Ausfall: die Aussenanbindung kappen
docker network disconnect netz-aussen hubspiegel
Danach die drei Fälle durchspielen: das gewärmte Image abrufen, ein nicht gespiegeltes abrufen, und den Spiegel mit docker restart hubspiegel neu starten. Die zwei Netze sind der wichtige Teil des Aufbaus. Ein Ausfall per DNS-Eintrag sieht überzeugend aus, wird aber von offenen Verbindungs-Pools unterlaufen, und dann misst man einen halben Ausfall.
Quellen:
- Docker-Dokumentation zu den Pull-Limits, Stand des Widerspruchs: 1. August 2026
- Dockers Rücknahme der Limit-Verschärfung vom März 2025
- Dockers Bericht zum Ausfall vom 20. Oktober 2025
- Docker Terms of Service, Abschnitt 4 und 4A
© 2026 Trutz Software Consulting GmbH. CC BY 4.0: Dieser Text darf mit Namensnennung weiterverwendet und bearbeitet werden, auch kommerziell. Code-Beispiele darin stehen unter Apache 2.0. Ausgenommen sind Marke und Logo sowie gekennzeichnete Zitate Dritter. Was das genau heißt.
Newsletter
Erhalte praxisnahe Tutorials, Best Practices und echte Lösungen für die häufigsten DevOps-Probleme direkt in Dein Postfach.
Datenschutz: Deine E-Mail-Adresse wird DSGVO-konform in Deutschland gespeichert. Keine Spam, nur relevante Inhalte. Eine E-Mail alle 1 bis 2 Wochen.