self hosted Docker Image Registry mit UI
Warum eine eigene Registry?
In dieser Anleitung wird eine Docker Registry eingerichtet und mittels Frontend zugängig gemacht.
Gründe hierfür können vielschichtig sein, zum Beispiel:
- eigene Images sind nicht öffentlich erreichbar (z.B. für selbstentwickelte Software)
- Cache um vom öffentlichen Netzwerk unabhängig zu sein
- keine Veränderung der Images, weil man selbst diue Kontrolle über die Registry hat
Idee
Es werden 2 Container gestartet, die Registry selbst und das Frontend. Das Frontend selbst benötigt keine Persistenz und die nötigen Informationen werden vom Browser beschafft.
Die UI verhält sich somit wie eine Clientanwendung, die selbst nicht mit der Registry spricht, sondern lediglich das visualisiert, was der Browser anfragt.
Die Registry und UI werden im selben nginx vHost laufen, können aber auch getrennt werden, hierzu sind weitere Einstellungen nötig, siehe Hinweise.
Zusätzlich kann das Frontend und die Registry mit Basic Auth geschützt werden, dies wird ebenfalls konfiguriert.
Konfiguration
Docker
Die REGISTRY_URL
muss auf den Hostname, unter dem die Registry erreichbar ist, gesetzt werden (z.B. myregistry.mydomain.com).
Ob Images gelöscht werden können, stellt REGISTRY_STORAGE_DELETE_ENABLED
sicher, das Frontend bietet die Option an, wenn DELETE_IMAGES
gesetzt ist.
Weil die Aktionen selbst vom Browser ausgeführt werden und nicht vom Frontend, sind beide Einstellungen auf true zu setzen.
Weitere Einstellungen können der Doku entnommen werden: https://github.com/Joxit/docker-registry-ui
Docker Compose
version: "3"
services:
registry:
container_name: registry
image: registry:2
restart: unless-stopped
volumes:
- /srv/docker/registry/data/:/var/lib/registry
environment:
REGISTRY_STORAGE_DELETE_ENABLED: "true"
networks:
default:
ipv4_address: 172.16.2.2
registry-ui:
container_name: registry-ui
image: joxit/docker-registry-ui:latest
restart: unless-stopped
environment:
REGISTRY_URL: "https://<REGISTRY_HOSTNAME>"
SINGLE_REGISTRY: "true"
DELETE_IMAGES: "true"
networks:
default:
ipv4_address: 172.16.2.3
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.16.2.0/24
driver_opts:
com.docker.network.bridge.name: br_registry
Danach können die Container gestartet werden.
HTTP Proxy (nginx)
Hier kann auch Apache, Traefik oder ein anderer Proxy der persönlichen Wahl genutzt werden.
Nginx ist aber bekannt und deswegen hier die spezifische Konfiguration für die beiden Dienste.
Um die Konfiguration einfach darstellen zu können, verzichte ich auf den ganzen Binding, SSL, Logging Overhead (siehe TODO)!
Basic Auth File generieren
Hier gibt es verschiedene Wege, Beispiele findest du hier: Anleitung zu Salted Password Hashes
Der Inhalt muss dann in die Datei /etc/nginx/htpasswd/myregistry.htpasswd
gepackt werden und hat den folgenden Syntax:
username:<PASSWORD_HASH>
test:<PASSWORD_HASH>
# siehe add_header 'Docker-Distribution-Api-Version'
map $upstream_http_docker_distribution_api_version $docker_distribution_api_version {
'' 'registry/2.0';
}
server {
################## TODO ###############################
ADD NGINX CONFIGURATION (LISTEN, LOG, SERVERNAME, ....)
#######################################################
# disable any limits to avoid HTTP 413 for large image uploads
client_max_body_size 0;
# required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
chunked_transfer_encoding on;
auth_basic "Registry realm";
auth_basic_user_file /etc/nginx/htpasswd/myregistry.htpasswd;
location / {
proxy_pass http://172.16.2.3:80;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
}
location /v2/ {
# Do not allow connections from docker 1.5 and earlier
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
return 404;
}
# To add basic authentication to v2 use auth_basic setting.
satisfy any;
# disable basic auth for specific IP
# allow x.x.x.x;
## If $docker_distribution_api_version is empty, the header is not added.
## See the map directive above where this variable is defined.
add_header 'Docker-Distribution-Api-Version' $docker_distribution_api_version always;
proxy_pass http:/172.16.2.3:5000;
proxy_set_header Host $http_host; # required for docker client's sake
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
}
}
Zugriff
Wenn man sich das Passwort gemerkt hat (…), sollte ein Login möglich sein:
docker login myregistry.domain.com
Der Login kann auch automatisch passieren, wenn in der Datei PASSWORD_FILE
das Klartextpasswort steht:
cat PASSWORD_FILE | docker login --username testuser
Hinweise
unterschiedliche Hostnames für Registry & Frontend
Soll das Frontend unter einer anderen URL als die Registry verfügbar gemacht werden, kann es nötig sein CORS Einstellungenin nginx vorzunehmen um dem Browser gültige URLs für API Calls mitzugeben. Links zum Thema:
- https://enable-cors.org/server_nginx.html
Ansonsten kann auch der nginx im Frontend Container als Proxy fungieren und hierfür sind Einstellungen vorgesehen: https://github.com/Joxit/docker-registry-ui?tab=readme-ov-file#using-cors.