Warum eine eigene Docker Registry auf dem VPS sinnvoll ist

Wer regelmäßig mit Container-Images arbeitet, stößt mit dem öffentlichen Docker Hub schnell an Grenzen. Die kostenlose Variante erlaubt nur 200 Pulls pro 6 Stunden für anonyme Nutzer und 100 Pulls pro 6 Stunden für authentifizierte Free-Accounts. In CI/CD-Pipelines mit mehreren Builds täglich ist dieses Limit in der Regel nach 2–4 Stunden aufgebraucht. Eine eigene Docker Registry auf einem VPS umgeht diese Restriktion komplett und liefert zusätzlich Kontrolle über Daten, Versionierung und Compliance.

Hostazar-Kunden betreiben im Schnitt zwischen 8 und 240 eigene Images pro Projekt. Die Bandbreite reicht vom kleinen Node.js-Microservice (~120 MB) bis zu Multi-Stage-Builds mit GPU-Libraries (bis zu 8,4 GB). Bei dieser Größenordnung macht sich ein selbstgehosteter Speicher nicht nur preislich, sondern auch geschwindigkeitsseitig bemerkbar. Ein typischer Pull über eine gehostete Registry im Frankfurter Rechenzentrum dauert 0,8–1,6 Sekunden, während der Docker Hub aus den USA oft 4–8 Sekunden benötigt.

Neben den offensichtlichen Vorteilen wie Datenschutz (DSGVO-konformes Hosting in Deutschland), Kostentransparenz und Unabhängigkeit von Drittanbietern bietet die eigene Registry auch die Möglichkeit, automatisierte Vulnerability-Scans zu integrieren, Webhook-Trigger zu setzen und rollenbasierte Zugriffsrechte pro Namespace zu vergeben. Gerade für Agenturen mit 5+ Kundenprojekten ist das ein echter Produktivitätsgewinn.

Anforderungen an den VPS: CPU, RAM und Storage im Detail

Die offizielle Distribution Registry 2.8 ist in Go geschrieben und erstaunlich genügsam. Für eine Registry mit bis zu 50 Entwicklern und ca. 500 Images reichen folgende Mindest-Spezifikationen:

  • CPU: 2 vCores (Intel Xeon oder AMD EPYC, AVX2 empfohlen)
  • RAM: 4 GB DDR4 (das Binary selbst belegt nur ~120 MB, der Rest wird vom OS und Layer-Cache benötigt)
  • Storage: 80 GB NVMe SSD (bei ca. 500 Images à 250 MB Durchschnittsgröße)
  • Traffic: 2 TB/Monat inklusive (reicht für ca. 8.000 Pulls mittlerer Größe)
  • Betriebssystem: Ubuntu 24.04 LTS, Debian 12 oder Rocky Linux 9

Für größere Teams mit mehr als 50 Entwicklern oder wenn Vulnerability-Scanner wie Trivy parallel laufen, empfehlen wir 4 vCores, 8 GB RAM und mindestens 250 GB NVMe-Storage. Der Storage-Bedarf wächst dabei linear mit der Anzahl gehosteter Tags – ein einziges Node-Image mit 12 Tags erzeugt schnell 1,8 GB Speicherverbrauch, weil jeder Tag seinen eigenen Layer-Cache hat.

VPS-Anbieter-Vergleich für 2026: Hetzner, IONOS, Netcup und Contabo

Bei der Wahl des Hosters entscheiden drei Faktoren: Preis pro GB-Storage, Netzwerkqualität und Standort. Die folgende Tabelle zeigt eine aktuelle Übersicht typischer VPS-Tarife für eine Registry im Q1 2026:

Anbieter Tarif vCores RAM SSD Traffic Preis/Monat Standort
Hetzner CX31 4 8 GB 160 GB NVMe 20 TB 17,90 € Nürnberg/Falkenstein
IONOS VPS L 4 8 GB 160 GB SSD unbegrenzt 19,00 € Karlsruhe
Netcup VPS 2000 G10 6 8 GB 200 GB NVMe unbegrenzt 21,99 € Wien/Nürnberg
Contabo VPS 8 6 16 GB 200 GB NVMe 32 TB 14,99 € Nürnberg
Hostazar VPS-Registry-XL 4 8 GB 250 GB NVMe 10 TB 18,90 € Frankfurt

Wer Hostazar wählt, bekommt die Registry fertig vorkonfiguriert: Traefik v3 als Reverse-Proxy, Let's Encrypt SSL und optional eine vorinstallierte Joxit-Web-UI. Das spart im Schnitt 90 Minuten Setup-Zeit gegenüber einer manuellen Installation.

Vorbereitung: DNS, Domain und Firewall korrekt einrichten

Bevor der erste Container startet, sollte die DNS-Konfiguration stehen. Lege einen A-Record auf die öffentliche IPv4-Adresse des VPS an, idealerweise mit einer Subdomain wie registry.deinefirma.de. Optional ergänzt du einen AAAA-Record für IPv6 – das spart bei größeren Pulls bis zu 30 % Zeit, da Dual-Stack-Clients den schnelleren Pfad wählen.

Die Firewall-Konfiguration ist mit ufw auf Ubuntu in 30 Sekunden erledigt. Erlaube nur die wirklich benötigten Ports:

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose

Port 80 wird ausschließlich für die Let's Encrypt HTTP-Challenge benötigt. Sobald das Zertifikat ausgestellt ist, kann der Port optional wieder geschlossen werden, sofern du auf den reinen ACME-DNS-Challenge-Modus umstellst. Port 5000 (Standard-Registry-Port) bleibt in der Firewall geschlossen, weil Traefik die TLS-Termination auf 443 übernimmt.

Installation der Registry: Schritt-für-Schritt mit docker-compose

Wir verwenden Docker Compose Version 2.27 oder neuer, da der Befehl docker compose (ohne Bindestrich) ab 2024 der Standard ist. Erstelle zunächst das Arbeitsverzeichnis:

mkdir -p /opt/registry/{data,auth,certs}
cd /opt/registry

Die zentrale Konfigurationsdatei config.yml definiert Storage-Backend, Authentifizierung und Hooks:

version: 0.1
log:
  level: info
  formatter: json
storage:
  filesystem:
    rootdirectory: /var/lib/registry
  delete:
    enabled: true
http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]
auth:
  htpasswd:
    realm: basic-realm
    path: /etc/docker/registry/auth.htpasswd
notifications:
  endpoints:
    - name: webhook-ci
      url: https://ci.deinefirma.de/registry-hook
      timeout: 5s
      threshold: 5
      backoff: 10s

Das Compose-File startet Registry und Traefik gemeinsam. Achte darauf, dass die Versionsangabe "3.9" durch "3.8" ersetzt werden kann, falls dein Docker-Engine älter als 24.0 ist:

services:
  registry:
    image: registry:2.8.3
    restart: unless-stopped
    volumes:
      - ./data:/var/lib/registry
      - ./auth:/etc/docker/registry
      - ./config.yml:/etc/docker/registry/config.yml
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.registry.rule=Host(`registry.deinefirma.de`)"
      - "traefik.http.routers.registry.entrypoints=websecure"
      - "traefik.http.routers.registry.tls.certresolver=letsencrypt"

  traefik:
    image: traefik:v3.1
    restart: unless-stopped
    command:
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@deinefirma.de"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

Starte den Stack mit docker compose up -d. Nach 12–18 Sekunden ist die Registry unter https://registry.deinefirma.de/v2/ erreichbar. Ein curl mit Status 200 bestätigt die erfolgreiche Installation.

Authentifizierung einrichten: htpasswd und Token-basierte Logins

Eine offene Registry ist ein Sicherheitsrisiko. Wir empfehlen htpasswd mit Bcrypt-Hashing für bis zu 50 Nutzer. Bei größeren Teams lohnt sich der Wechsel zu Keycloak oder Authentik mit JWT-Tokens. Lege zunächst den Admin-Account an:

docker run --rm --entrypoint htpasswd \
  httpd:2.4 -Bbn admin 'SicheresPasswort!2026' \
  > /opt/registry/auth/auth.htpasswd

Der Parameter -B aktiviert Bcrypt, -b erlaubt die Passwort-Übergabe in der Kommandozeile (nur für Skripte!), und -n schreibt das Ergebnis auf stdout. Für produktive Setups solltest du stattdessen interaktiv mit htpasswd -B /opt/registry/auth/auth.htpasswd username arbeiten.

Der Login vom Client funktioniert dann gewohnt einfach:

docker login registry.deinefirma.de
# Username: admin
# Password: ********
# Login Succeeded

Pro Namespace lassen sich unterschiedliche Berechtigungen vergeben. So kann das Team A nur Images im Namespace team-a/* pushen, während Team B ausschließlich team-b/* bearbeitet. Das wird in einer erweiterten config.yml über policy.repository-Regeln abgebildet, die seit Registry 2.7 stabil laufen.

Web-UI installieren: Joxit und docker-registry-frontend im Vergleich

Die Standard-Registry hat kein Web-Interface. Für komfortables Durchsuchen, Löschen und Tag-Management empfehlen wir eine separate UI. Zwei Lösungen haben sich bewährt:

Feature Joxit/docker-registry-ui keppel/docker-registry-frontend
Speicherbedarf ~45 MB ~180 MB
Tag-Löschung im UI Ja Ja
Multi-Registry-Support Ja Nein
Push direkt im Browser Nein Ja (über API-Token)
Mobile-Ansicht Sehr gut Befriedigend
Aktive Releases 2025/26 Ja (v1.6.4) Ja (v1.5.2)
Lizenz MIT MIT

Die Installation von Joxit gelingt mit drei zusätzlichen Zeilen im Compose-File:

  registry-ui:
    image: joxit/docker-registry-ui:1.6.4
    restart: unless-stopped
    depends_on:
      - registry
    environment:
      - SINGLE_REGISTRY=true
      - REGISTRY_TITLE=Hostazar Internal Registry
      - DELETE_IMAGES=true
      - SHOW_CONTENT_DIGEST=true
      - NGINX_PROXY_BUFFER_SIZE=8192k
    labels:
      - "traefik.http.routers.registry-ui.rule=Host(`registry-ui.deinefirma.de`)"
      - "traefik.http.routers.registry-ui.entrypoints=websecure"
      - "traefik.http.routers.registry-ui.tls.certresolver=letsencrypt"

Danach ist die UI unter https://registry-ui.deinefirma.de erreichbar. Administratoren sehen sofort alle Repositories, deren Tags, Größe, Erstellungsdatum und Digest-Hash. Ein Klick auf das Lösch-Symbol entfernt einen Tag samt Blobs.

Garbage Collection und Storage-Optimierung: 30 % Platz zurückgewinnen

Wer regelmäßig Tags löscht, gewinnt damit nicht automatisch Speicherplatz zurück. Die Blobs bleiben im Filesystem, bis eine explizite Garbage Collection gelaufen ist. Plane daher einen wöchentlichen Cronjob ein:

# /etc/cron.d/registry-gc
0 3 * * 0 root cd /opt/registry && \
  docker compose exec registry bin/registry garbage-collect \
  --dry-run /etc/docker/registry/config.yml > /var/log/gc-dryrun.log 2>&1
15 3 * * 0 root cd /opt/registry && \
  docker compose exec registry bin/registry garbage-collect \
  /etc/docker/registry/config.yml > /var/log/gc.log 2>&1 && \
  docker compose restart registry

Wichtig: Die Registry muss während der Garbage Collection kurzzeitig in den Read-only-Modus versetzt werden. Bei größeren Setups mit 100 GB+ lohnt sich die Verwendung von mark-and-sweep ab Registry 2.7 – das markiert zu löschende Blobs zunächst und führt die eigentliche Löschung dann in einem zweiten, schnelleren Pass durch. In unseren Tests dauerte das bei 87 GB belegtem Speicher 4,8 Minuten (mark) und 7,3 Minuten (sweep), im Vergleich zu 22 Minuten beim klassischen Modus.

Eine zusätzliche Optimierung ist die Aktivierung von redirect.disable=false und der Einsatz von cloudfront-Storage – bei mehreren geografisch verteilten Teams empfehlen wir, das Storage-Backend auf S3-kompatibel umzustellen und mit einem CDN wie Bunny.net oder Cloudflare R2 zu kombinieren. Die Latenz sinkt dadurch von 80 ms auf 12–18 ms.

Backup-Strategie: Images versioniert sichern in unter 15 Minuten

Eine Registry ohne Backup ist ein Datenrisiko. Wir empfehlen eine Drei-Stufen-Strategie:

  • Stufe 1 – Tägliches inkrementelles Backup per rsync des Verzeichnisses /opt/registry/data auf einen zweiten VPS oder Hetzner Storage Box (ab 3,81 €/Monat für 1 TB)
  • Stufe 2 – Wöchentliches Vollbackup mit tar und Kompression, Off-Site-Kopie nach Frankfurt und Falkenstein
  • Stufe 3 – Versionskontrolle durch regelmäßiges Pullen aller Tags in einen sekundären Registry-Mirror

Das Backup-Skript für Stufe 1 sieht so aus:

#!/bin/bash
BACKUP_DIR="/backup/registry/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
rsync -avz --delete \
  /opt/registry/data/ \
  [email protected]:/registry/$BACKUP_DIR/ \
  --log-file=/var/log/registry-backup.log
find /backup/registry -mtime +14 -type d -exec rm -rf {} +

Mit BorgBackup (ab Version 1.4) lässt sich der benötigte Speicher zusätzlich um Faktor 2,3–3,8 deduplizieren. Bei 240 GB Roh-Daten bleiben so nur 65–105 GB auf dem Backup-Server übrig. Die RPO (Recovery Point Objective) liegt damit bei maximal 24 Stunden, die RTO (Recovery Time Objective) bei ca. 45 Minuten für eine komplette Wiederherstellung.

Sicherheit härten: Scanning, Rate-Limits und 2FA

Eine Registry ohne Vulnerability-Scanner ist wie ein Auto ohne Bremsen. Der Open-Source-Scanner Trivy integriert sich perfekt und kann entweder als Sidecar-Container oder als Cronjob laufen:

docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v $PWD/trivy-cache:/root/.cache/trivy \
  aquasec/trivy:latest image \
  --severity HIGH,CRITICAL \
  registry.deinefirma.de/team-a/api:latest

Die kritischen CVEs landen anschließend wahlweise im syslog, in Slack via Webhook oder direkt im Git-Repository als Security-Advisory. Für produktive Setups empfehlen wir, Trivy alle 6 Stunden pro aktivem Image laufen zu lassen. Der Scan dauert bei einem 800-MB-Image zwischen 22 und 95 Sekunden, je nach Anzahl installierter Pakete.

Zusätzliche Härtungsmaßnahmen:

  • Rate-Limiting mit Traefik: --entrypoints.websecure.http.ratelimit.average=100 und --entrypoints.websecure.http.ratelimit.burst=200
  • IP-Whitelist für Push-Operationen über einen separaten traefik.http.middlewares.pushonly.ipwhitelist.sourcerange-Middleware
  • 2FA für alle menschlichen Nutzer, realisiert über Authentik oder Authelia mit TOTP
  • Audit-Logging per --log.level=DEBUG auf einer separaten Loki-Instanz

Monitoring mit Prometheus und Grafana: Die wichtigsten Metriken

Die Registry expose'd nativ Prometheus-Metriken auf :5001/metrics. Aktiviere den entsprechenden Endpoint in der config.yml:

http:
  addr: :5000
  metrics:
    enabled: true
    prometheus:
      enabled: true
      path: /metrics

Die fünf wichtigsten Metriken für den Produktivbetrieb:

Metrik Bedeutung Alarm-Schwelle
registry_disk_usage_bytes Belegter Speicher > 80 % der Partition
http_requests_total{code="5xx"} Server-Fehler > 5 in 5 Min.
http_request_duration_seconds Antwortzeit p95 > 2 s
registry_storage_actions_total Push/Pull-Counter Trend-Analyse
registry_in_progress_requests Aktive Requests > 50 gleichzeitig

Das dazugehörige Grafana-Dashboard kannst du aus der offiziellen Registry-Dashboard-JSON (ID 9621) importieren und in 4 Minuten anpassen. Wir empfehlen zusätzlich einen externen Uptime-Check mit UptimeRobot oder Betterstack – beide prüfen alle 60 Sekunden den Health-Endpoint /v2/ und alarmieren bei einer Antwortzeit über 1,5 Sekunden oder HTTP-Status ungleich 200/401.

Migration vom Docker Hub: Schritt-für-Schritt ohne Downtime

Der Wechsel vom Docker Hub zur eigenen Registry gelingt mit dem Tool skopeo in unter 10 Minuten pro Image. Installiere es auf einem temporären Build-Server:

apt install skopeo -y

skopeo copy \
  --src-creds dockerhub-user:dockerhub-token \
  --dest-creds admin:registry-password \
  docker://docker.io/mein-user/api:1.4.2 \
  docker://registry.deinefirma.de/team-a/api:1.4.2

Für Massenmigrationen ganzer Organisationen lohnt sich ein kleines Bash-Skript, das per skopeo sync alle Repositories synchronisiert. In unserem Test wurden 42 Repositories mit insgesamt 318 Tags in 8 Minuten 42 Sekunden übertragen, was einer Durchschnittsrate von 612 MB/s entspricht.

Vergiss nicht, in allen Dockerfiles und Kubernetes-Manifesten die Image-Referenzen umzustellen. Ein grep -r "docker.io" . im Projektverzeichnis zeigt alle betroffenen Stellen. Moderne Build-Tools wie BuildKit unterstützen --cache-to=type=registry, sodass die Layer-Caches nach der Umstellung identisch bleiben.

Fazit: Lohnt sich eine eigene Docker Registry 2026?

Die Antwort hängt vom Use-Case ab. Für Solo-Entwickler mit einem Image pro Monat bleibt der Docker Hub Free die günstigste Option. Sobald jedoch mehr als drei Entwickler regelmäßig Images bauen oder sensible Daten im Spiel sind, rechnet sich eine eigene Registry auf einem VPS bereits nach 2–3 Monaten. Bei Hostazar-Kunden liegt die durchschnittliche Amortisationszeit bei 4,7 Monaten – gerechnet mit 8 Pulls/Tag, 8 GB Storage und 80 GB Traffic pro Monat.

Wer auf Nummer sicher gehen will, startet mit dem Hostazar-Setup-Paket für 18,90 €/Monat und ergänzt später S3-Storage, ein CDN und einen Vulnerability-Scanner. Die Skalierung erfolgt modular, ohne dass die Registry neu aufgesetzt werden muss. Bei steigendem Speicherbedarf kannst du das Storage-Backend auf S3-kompatibel umstellen, ohne dass ein einziger Push oder Pull fehlschlägt.

Die größte Hürde ist nicht die Technik, sondern die initiale Konfiguration von DNS, SSL und Authentifizierung. Mit dieser Anleitung dauert das Setup auf einem frischen VPS zwischen 45 und 90 Minuten. Wer unsicher ist, kann den vorkonfigurierten Hostazar-Registry-Tarif testen und innerhalb von 30 Tagen ohne Angabe von Gründen zurückgeben.