diff --git a/ansible/group_vars/all/vars.yml b/ansible/group_vars/all/vars.yml index 88603b0..12a5c62 100644 --- a/ansible/group_vars/all/vars.yml +++ b/ansible/group_vars/all/vars.yml @@ -96,8 +96,9 @@ act_runner_count: 3 # Secrets — store values in ansible/group_vars/all/vault.yml (Ansible Vault) # gitea_runner_registration_token: "" # Gitea admin → Site Administration → Runners → Create runner token -# Container registry (Docker Registry v2) +# Container registry (Docker Registry v2 + web UI) registry_data_dir: /opt/registry +registry_ui_version: "main" # Registry users — plaintext passwords, Ansible generates bcrypt hashes at deploy time # registry_users: # - username: alice diff --git a/ansible/roles/registry/templates/docker-compose.yml.j2 b/ansible/roles/registry/templates/docker-compose.yml.j2 index 6d617fc..0f4fb6a 100644 --- a/ansible/roles/registry/templates/docker-compose.yml.j2 +++ b/ansible/roles/registry/templates/docker-compose.yml.j2 @@ -14,13 +14,40 @@ services: - {{ registry_data_dir }}/auth:/auth:ro networks: - traefik_public + - registry_internal labels: - "traefik.enable=true" - - "traefik.http.routers.registry.rule=Host(`{{ domain_registry }}`)" - - "traefik.http.routers.registry.entrypoints=websecure" - - "traefik.http.routers.registry.tls.certresolver=letsencrypt" + # PathPrefix(/v2) is more specific than the UI's bare Host rule — Traefik routes docker CLI traffic here + - "traefik.http.routers.registry-api.rule=Host(`{{ domain_registry }}`) && PathPrefix(`/v2`)" + - "traefik.http.routers.registry-api.entrypoints=websecure" + - "traefik.http.routers.registry-api.tls.certresolver=letsencrypt" - "traefik.http.services.registry.loadbalancer.server.port=5000" + registry-ui: + image: joxit/docker-registry-ui:{{ registry_ui_version }} + container_name: registry-ui + restart: unless-stopped + environment: + - SINGLE_REGISTRY=true + - REGISTRY_SECURED=true + - REGISTRY_URL=https://{{ domain_registry }} + - DELETE_IMAGES=true + - SHOW_CATALOG_NB_TAGS=true + - SHOW_CONTENT_DIGEST=true + - CATALOG_ELEMENTS_LIMIT=1000 + - TAGLIST_PAGE_SIZE=100 + - REGISTRY_TITLE=Container Registry + networks: + - traefik_public + labels: + - "traefik.enable=true" + - "traefik.http.routers.registry-ui.rule=Host(`{{ domain_registry }}`)" + - "traefik.http.routers.registry-ui.entrypoints=websecure" + - "traefik.http.routers.registry-ui.tls.certresolver=letsencrypt" + - "traefik.http.services.registry-ui.loadbalancer.server.port=80" + networks: traefik_public: external: true + registry_internal: + internal: true diff --git a/docs/runbook-configuration.md b/docs/runbook-configuration.md index d2c75b4..adff9fa 100644 --- a/docs/runbook-configuration.md +++ b/docs/runbook-configuration.md @@ -411,6 +411,8 @@ The file is then available at: ## 8. Container registry +### 8.1 Docker CLI usage + ```bash # Login docker login cr.ladkau.de @@ -431,3 +433,13 @@ user, edit the vault and redeploy: ansible-vault edit ansible/group_vars/all/vault.yml ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags registry --ask-vault-pass ``` + +### 8.2 Registry web UI + +A web dashboard is available at `https://cr.ladkau.de/`. Sign in with any +`registry_users` credential from the vault. The UI allows browsing repositories +and tags, inspecting image manifests and digests, and deleting images. + +Traefik routes `/v2/` (Docker API) to the registry container and everything else +to the UI container — both on the same domain, so no CORS configuration is +needed. `docker push` and `docker pull` work exactly as before.