Lokale Docker Registry als Pull-Through-Cache: CI-Builds beschleunigen
AI generated
FROM
RUN
Docker · CI/CD · Registry
Lokale Registry als Pull-Through-Cache
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.

17 Min. Lesezeit Pull-Through-Cache Rate Limits CI/CD

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.

11. FAQ: Pull-Through-Cache: Das Wichtigste auf einen Blick

1Was ist ein Pull-Through-Cache bei Docker?
Ein Pull-Through-Cache ist eine eigene Registry-Instanz, die Anfragen an eine Upstream-Registry wie Docker Hub weiterleitet, das Ergebnis lokal speichert und bei wiederholten Anfragen aus dem eigenen Speicher ausliefert, ohne den Upstream erneut zu kontaktieren.
2Wie hoch sind die aktuellen Docker Hub Rate Limits?
Anonyme Nutzer duerfen 100 Pulls pro sechs Stunden pro IP-Adresse durchfuehren, authentifizierte kostenlose Konten 200 Pulls im selben Zeitraum. Kostenpflichtige Plaene erhoehen das Limit deutlich, die genauen Werte aendert Docker gelegentlich, sodass ein aktueller Blick in die Docker-Hub-Dokumentation sinnvoll ist.
3Muessen Dockerfiles fuer den Cache angepasst werden?
Nein, wenn der Docker-Daemon der CI-Runner ueber registry-mirrors konfiguriert wird, laufen alle Pulls von docker.io automatisch ueber den Cache, ohne dass Image-Namen in Dockerfiles oder Pipeline-Definitionen geaendert werden muessen.
4Was passiert, wenn ein Image im Upstream aktualisiert wird?
Die Registry prueft bei jedem Pull per HEAD-Request den aktuellen Digest im Upstream und laedt bei Abweichung automatisch neu, solange die Verbindung zum Upstream besteht. So bekommen Clients trotz Caching immer den aktuellen Stand veraenderlicher Tags.
5Wie wird der Cache-Speicher begrenzt?
Ueber REGISTRY_PROXY_TTL laeuft ein Cache-Eintrag nach einer festgelegten Zeit ab, zusaetzlich entfernt das eingebaute Garbage-Collection-Kommando regelmaessig Blobs, die von keinem Manifest mehr referenziert werden.
6Kann ich mehrere Upstream-Registries gleichzeitig cachen?
Ja, aber jede Registry-Instanz kann nur einen Upstream im Proxy-Modus bedienen. Fuer Docker Hub, GHCR und Quay parallel werden entsprechend drei separate Cache-Instanzen mit eigenem Port benoetigt.
7Lohnt sich ein authentifizierter Zugang zum Cache?
Bei kleinen Teams reicht oft der anonyme Zugang mit 100 Pulls pro sechs Stunden zentral fuer alle Runner. Bei groesseren CI-Flotten mit vielen taeglichen Builds hebt ein authentifizierter Docker-Hub-Nutzer das Limit deutlich an.
8Ist der Pull-Through-Cache ein Ersatz fuer eine eigene Registry?
Nein, der Cache spiegelt nur bereits existierende Images aus einem Upstream. Fuer selbst gebaute Images wird weiterhin eine eigene, beschreibbare Registry benoetigt, die auch getrennt vom Cache betrieben werden kann.
9Wie stelle ich Hochverfuegbarkeit fuer den Cache sicher?
Mehrere Registry-Instanzen hinter einem Load Balancer, die sich ein gemeinsames Storage-Backend wie S3-kompatiblen Objektspeicher teilen, vermeiden einen Single Point of Failure gegenueber einer einzelnen Instanz mit lokalem Volume.
10Wann lohnt sich Harbor statt der einfachen registry:2 Loesung?
Sobald zusaetzlich RBAC, Vulnerability-Scanning oder Multi-Registry-Replikation gebraucht werden, ist Harbor die passendere Wahl. Fuer den reinen Zweck, CI-Rate-Limits zu umgehen, reicht die schlanke Proxy-Konfiguration von registry:2 meist aus.