Docker Hub Rate Limits umgehen und CI-Pull-Zeiten drastisch senken
Wer in CI-Pipelines regelmaessig gegen Docker-Hub-Rate-Limits laeuft, kann mit einer lokalen Registry als Pull-Through-Cache Pull-Zeiten senken und Ausfaelle vermeiden, ohne Images manuell zu spiegeln.
Inhaltsverzeichnis
- 1. Das Problem: Docker Hub Rate Limits in CI-Pipelines
- 2. Wie ein Pull-Through-Cache funktioniert
- 3. Registry als Pull-Through-Cache einrichten
- 4. CI-Runner auf die lokale Registry umbiegen
- 5. Docker-Hub-Zugangsdaten fuer hoehere Limits hinterlegen
- 6. Cache-Invalidierung und Tag-Aktualisierung
- 7. Speicherverwaltung und Garbage Collection
- 8. Cache-Hit-Rate ueberwachen
- 9. Grenzen des Ansatzes und Alternativen
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem: Docker Hub Rate Limits in CI-Pipelines
Docker Hub begrenzt anonyme Pulls auf 100 Anfragen pro sechs Stunden und authentifizierte Pulls auf 200 Anfragen im selben Zeitraum, je nach Kontotyp auch weniger. In einer CI-Umgebung mit mehreren parallelen Pipelines, die jeweils Basis-Images, Build-Tools und Service-Container ziehen, ist dieses Limit schnell erreicht, besonders wenn viele Runner hinter derselben oeffentlichen IP-Adresse sitzen und sich das Limit faktisch teilen.
Die Folge sind fehlschlagende Builds mit der Fehlermeldung 'toomanyrequests', die scheinbar zufaellig auftreten und in der Fehlersuche viel Zeit kosten, weil sie nicht am Code liegen, sondern an der Infrastruktur. Ein Pull-Through-Cache loest dieses Problem strukturell: Statt jeden Pull einzeln gegen Docker Hub abzusetzen, bedient die Cache-Registry wiederholte Anfragen aus dem lokalen Speicher und reduziert die tatsaechlichen Docker-Hub-Zugriffe auf ein Minimum.
2. Wie ein Pull-Through-Cache funktioniert
Technisch ist ein Pull-Through-Cache eine normale Docker Registry (das offizielle registry:2-Image), die zusaetzlich im Proxy-Modus laeuft. Beim ersten Pull eines Images leitet die Cache-Registry die Anfrage an Docker Hub weiter, speichert das Ergebnis lokal und liefert es aus. Jeder weitere Pull desselben Images und Tags wird direkt aus dem lokalen Speicher bedient, ohne dass Docker Hub erneut kontaktiert wird, solange der Cache-Eintrag noch gueltig ist.
Fuer Clients aendert sich dabei wenig: Statt docker.io wird die eigene Registry als Präfix im Image-Namen verwendet, oder der Docker-Daemon wird ueber registry-mirrors so konfiguriert, dass er automatisch ueber die lokale Registry geht. Aus Sicht der Pipeline sieht das Verhalten identisch aus, nur dass wiederholte Pulls binnen Millisekunden statt Sekunden abgeschlossen sind und keine externen Rate Limits mehr greifen.
3. Registry als Pull-Through-Cache einrichten
Die Einrichtung erfolgt ueber eine Konfigurationsdatei fuer die Registry, in der ein proxy-Block mit remoteurl auf https://registry-1.docker.io zeigt. Das registry:2-Image liest diese Konfiguration beim Start ein und verhaelt sich danach transparent als Cache vor Docker Hub. Wichtig ist, dass eine Cache-Registry pro Upstream betrieben wird: Wer zusaetzlich GHCR oder Quay spiegeln will, braucht dafuer jeweils eine eigene Registry-Instanz mit eigenem Port.
Persistenter Speicher fuer die Registry-Daten ist Pflicht, sonst geht der Cache bei jedem Neustart verloren und die erste Welle an Builds nach einem Deploy trifft wieder ungebremst auf Docker Hub. Ein dediziertes Volume fuer /var/lib/registry reicht dafuer aus und sollte regelmaessig auf Speicherverbrauch geprueft werden, da der Cache ohne Aufraeumen kontinuierlich waechst.
# docker-compose.yml
services:
registry-cache:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_PROXY_REMOTEURL: "https://registry-1.docker.io"
REGISTRY_PROXY_TTL: "168h"
REGISTRY_STORAGE_DELETE_ENABLED: "true"
volumes:
- registry-cache-data:/var/lib/registry
restart: unless-stopped
volumes:
registry-cache-data:
4. CI-Runner auf die lokale Registry umbiegen
Damit CI-Jobs den Cache tatsaechlich nutzen, muss der Docker-Daemon der Runner ueber registry-mirrors in der daemon.json konfiguriert werden. Dieser Eintrag sorgt dafuer, dass alle Pulls von docker.io automatisch ueber die angegebene Mirror-URL laufen, ohne dass Dockerfiles oder CI-Skripte angepasst werden muessen. Das ist der entscheidende Vorteil gegenueber manuellem Image-Retagging: Bestehende Pipelines profitieren sofort, ohne Codeaenderungen.
Bei selbst gehosteten Runnern (GitLab Runner, Jenkins-Agents, GitHub Actions Runner in eigener Infrastruktur) ist das unproblematisch, da der Daemon direkt konfigurierbar ist. Bei gehosteten CI-Anbietern ohne Zugriff auf den Docker-Daemon funktioniert der Ansatz nur eingeschraenkt, dort bleibt oft nur die explizite Umbenennung im image-Feld der Pipeline-Definition als Alternative.
# /etc/docker/daemon.json auf dem CI-Runner
{
"registry-mirrors": ["http://registry-cache.internal:5000"]
}
# Danach Docker-Daemon neu starten
sudo systemctl restart docker
# Test: Pull sollte jetzt ueber den Cache laufen
docker pull node:20-alpine
docker logs registry-cache 2>&1 | grep "GET /v2/node""
5. Docker-Hub-Zugangsdaten fuer hoehere Limits hinterlegen
Ohne Zugangsdaten laeuft die Cache-Registry als anonymer Client gegen Docker Hub und unterliegt selbst dem anonymen Rate Limit von 100 Pulls pro sechs Stunden, nur eben zentral fuer alle Runner statt pro Runner. Das reicht bei kleinen Teams oft schon aus, bei groesseren CI-Flotten mit vielen taeglichen Builds lohnt sich ein authentifizierter Zugang, der das Limit auf 200 Pulls anhebt oder bei einem kostenpflichtigen Docker-Hub-Plan noch weiter erhoeht.
Die Zugangsdaten werden ueber REGISTRY_PROXY_USERNAME und REGISTRY_PROXY_PASSWORD als Umgebungsvariablen der Cache-Registry hinterlegt, idealerweise als Secret und nicht als Klartext im Compose-File. Ein eigens dafuer angelegter Docker-Hub-Nutzer mit Read-Only-Berechtigung ist sinnvoll, damit ein kompromittierter Cache-Server nicht gleich Schreibzugriff auf produktive Repositories hat.
services:
registry-cache:
image: registry:2
environment:
REGISTRY_PROXY_REMOTEURL: "https://registry-1.docker.io"
REGISTRY_PROXY_USERNAME: "${DOCKERHUB_CACHE_USER}"
REGISTRY_PROXY_PASSWORD: "${DOCKERHUB_CACHE_TOKEN}"
env_file:
- .env.registry-cache
6. Cache-Invalidierung und Tag-Aktualisierung
Der kritische Punkt bei jedem Pull-Through-Cache ist die Behandlung veraenderlicher Tags wie latest oder 20-alpine, die im Upstream regelmaessig neu gebaut werden. Die Registry prueft bei jedem Pull-Versuch ueber einen HEAD-Request, ob sich der Digest des Tags geaendert hat, und laedt bei Abweichung automatisch neu nach. Solange das Netzwerk zu Docker Hub erreichbar ist, bekommen Clients also immer den aktuellen Stand, auch wenn der Cache schon einen aelteren Layer vorhaelt.
REGISTRY_PROXY_TTL steuert, wie lange ein Cache-Eintrag maximal vorgehalten wird, bevor er unabhaengig vom Digest-Check als abgelaufen gilt und vollstaendig neu gezogen wird. Fuer CI-Umgebungen ist ein TTL von etwa einer Woche ein guter Kompromiss: kurz genug, um Sicherheitsupdates in Basis-Images zeitnah zu bekommen, lang genug, um die Trefferquote hoch zu halten und Docker Hub nicht unnoetig zu belasten.
7. Speicherverwaltung und Garbage Collection
Ein Pull-Through-Cache waechst kontinuierlich, weil jedes neu angefragte Image dauerhaft gespeichert bleibt, auch wenn es nie wieder gepullt wird. Ohne Aufraeumen fuellt sich das Volume irgendwann komplett, was im schlechtesten Fall dazu fuehrt, dass die Registry keine neuen Layer mehr annehmen kann und Builds fehlschlagen, obwohl das eigentliche Rate-Limit-Problem laengst geloest ist.
Die Registry bringt dafuer ein eingebautes Garbage-Collection-Kommando mit, das ungenutzte Blobs entfernt, die von keinem Manifest mehr referenziert werden. Es muss bei laufendem Proxy-Modus im Read-Only-Fenster ausgefuehrt werden, damit waehrend des Laufs keine neuen Layer geschrieben werden, am einfachsten als geplanter Wartungsjob ausserhalb der Hauptarbeitszeit der CI-Pipelines.
# Garbage Collection im Registry-Container ausfuehren
docker exec registry-cache \
registry garbage-collect /etc/docker/registry/config.yml
# Aktuelle Speichernutzung des Cache-Volumes pruefen
docker system df -v | grep registry-cache-data
8. Cache-Hit-Rate ueberwachen
Ob sich der Aufwand fuer den Pull-Through-Cache lohnt, zeigt sich erst im Monitoring: Die Registry loggt jeden eingehenden Request inklusive Pfad, sodass sich Treffer und tatsaechliche Upstream-Anfragen aus den Logs unterscheiden lassen. Ein einfacher Anfang ist das Parsen der Log-Zeilen nach Pull-Requests und der Abgleich, wie viele davon tatsaechlich an registry-1.docker.io weitergereicht wurden.
Fuer dauerhaftes Monitoring lohnt sich der Export der Registry-Metriken ueber den eingebauten Prometheus-Endpunkt, der unter anderem Request-Zaehler pro Statuscode liefert. Damit laesst sich in Grafana ein einfaches Dashboard mit Cache-Hit-Rate und verbleibendem Docker-Hub-Kontingent aufbauen, das fruehzeitig zeigt, wenn sich die Nutzung eines Teams veraendert und das Limit trotz Cache wieder knapp wird.
9. Grenzen des Ansatzes und Alternativen
Ein einzelner Pull-Through-Cache ist ein Single Point of Failure: Faellt die Registry aus, faellt der gesamte CI-Betrieb auf direkte Docker-Hub-Pulls zurueck, im ungluecklichsten Fall genau dann, wenn das Rate Limit ohnehin knapp ist. Fuer produktive Umgebungen empfiehlt sich daher ein redundantes Setup mit mindestens zwei Registry-Instanzen hinter einem Load Balancer, die sich denselben Storage-Backend teilen, etwa ein S3-kompatibles Objekt-Storage statt eines lokalen Volumes.
Wer mehr braucht als reines Caching, etwa RBAC, Vulnerability-Scanning oder Multi-Registry-Replikation, landet frueher oder spaeter bei ausgewachsenen Loesungen wie Harbor oder Sonatype Nexus Repository. Beide unterstuetzen ebenfalls Pull-Through-Caching, bringen aber deutlich mehr Betriebsaufwand mit. Fuer den reinen Zweck, Rate Limits in CI zu umgehen, ist die schlanke registry:2-Loesung meist der pragmatischere Einstieg.
| Loesung | Setup-Aufwand | Zusatzfunktionen | Empfehlung |
|---|---|---|---|
| registry:2 Proxy-Modus | Gering, ein Container | Nur Caching | CI-Rate-Limits schnell loesen |
| Harbor | Mittel, mehrere Komponenten | RBAC, Scanning, Replikation | Teamweite Registry-Plattform |
| Sonatype Nexus Repository | Mittel bis hoch | Multi-Format, Proxy fuer viele Ökosysteme | Bereits vorhandene Nexus-Infrastruktur |
| Kein Cache, direkte Pulls | Keiner | Keine | Nur bei sehr wenigen taeglichen Builds |
Mironsoft
Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung
Docker-Setups, die im Team und in Produktion tragfähig bleiben?
Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.
Dockerfile-Review
Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.
Security-Audit
Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.
CI/CD-Integration
Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.
10. Zusammenfassung
Pull-Through-Cache: Das Wichtigste auf einen Blick
Problem
Docker Hub Rate Limits blockieren CI-Pipelines mit vielen parallelen Pulls.
Loesung
registry:2 im Proxy-Modus als lokaler Pull-Through-Cache vor Docker Hub.
Integration
registry-mirrors in daemon.json, keine Aenderung an Dockerfiles noetig.
Betrieb
TTL fuer Aktualitaet, Garbage Collection gegen unbegrenztes Wachstum.