Add Docker registry UI and tag cleanup automation
Deploy joxit/docker-registry-ui on port 8080 for browsing images/tags. Add Python script to prune old registry tags (keeps last N per image), scheduled daily at 2am via cron. Expose UI via reverse proxy at registry.viktorbarzin.me with Authentik auth.
This commit is contained in:
parent
f8c25d9c23
commit
11d328fb99
3 changed files with 94 additions and 1 deletions
9
main.tf
9
main.tf
|
|
@ -276,6 +276,12 @@ module "docker-registry-template" {
|
||||||
templatefile("${path.root}/modules/docker-registry/nginx_registry.conf", {})
|
templatefile("${path.root}/modules/docker-registry/nginx_registry.conf", {})
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
"docker run -d --restart always --net host --name registry-ui -e NGINX_LISTEN_PORT=8080 -e NGINX_PROXY_PASS_URL=http://127.0.0.1:5000 -e DELETE_IMAGES=true -e SINGLE_REGISTRY=true -e SHOW_CONTENT_DIGEST=true -e SHOW_CATALOG_NB_TAGS=true -e CATALOG_ELEMENTS_LIMIT=1000 -e TAGLIST_PAGE_SIZE=100 -e REGISTRY_TITLE=viktorbarzin.me joxit/docker-registry-ui:latest",
|
||||||
|
# Deploy tag cleanup script (keep last 10 tags per image) and schedule daily at 2am before weekly GC
|
||||||
|
format("echo %s | base64 -d > /etc/docker-registry/cleanup-tags.sh && chmod +x /etc/docker-registry/cleanup-tags.sh",
|
||||||
|
base64encode(file("${path.root}/modules/docker-registry/cleanup-tags.sh"))
|
||||||
|
),
|
||||||
|
"( crontab -l 2>/dev/null; echo '0 2 * * * /etc/docker-registry/cleanup-tags.sh 10 >> /var/log/registry-cleanup.log 2>&1' ) | crontab -",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,7 +304,8 @@ module "docker-registry-vm" {
|
||||||
# ports:
|
# ports:
|
||||||
# 5000 -> registry
|
# 5000 -> registry
|
||||||
# 5001 -> metrics
|
# 5001 -> metrics
|
||||||
# 5002 -> ngin proxy <-- use this to prevent races on the same blobs
|
# 5002 -> nginx proxy <-- use this to prevent races on the same blobs
|
||||||
|
# 8080 -> registry-ui (joxit/docker-registry-ui)
|
||||||
}
|
}
|
||||||
|
|
||||||
# module that provisions the proxmox host?
|
# module that provisions the proxmox host?
|
||||||
|
|
|
||||||
72
modules/docker-registry/cleanup-tags.sh
Normal file
72
modules/docker-registry/cleanup-tags.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Keeps only the N most recent tags per image in a Docker registry.
|
||||||
|
Uses filesystem modification times on the tag directories for speed.
|
||||||
|
Run garbage-collect after this to reclaim disk space."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(line_buffering=True)
|
||||||
|
|
||||||
|
REGISTRY = "http://127.0.0.1:5000"
|
||||||
|
KEEP = int(sys.argv[1]) if len(sys.argv) > 1 else 10
|
||||||
|
|
||||||
|
# Registry storage path (docker volume)
|
||||||
|
STORAGE = "/var/lib/docker/volumes/57b3f1c5fcc7f39c040e17072e10b4536245357d09340206683c04096d30b942/_data/docker/registry/v2/repositories"
|
||||||
|
|
||||||
|
def api(path, method="GET", headers=None):
|
||||||
|
req = urllib.request.Request(f"{REGISTRY}{path}", method=method, headers=headers or {})
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as r:
|
||||||
|
if method == "HEAD":
|
||||||
|
return dict(r.headers)
|
||||||
|
return json.loads(r.read())
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get all repos
|
||||||
|
catalog = api("/v2/_catalog")
|
||||||
|
if not catalog:
|
||||||
|
print("Failed to fetch catalog")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
total_deleted = 0
|
||||||
|
|
||||||
|
for repo in catalog.get("repositories", []):
|
||||||
|
tags_dir = os.path.join(STORAGE, repo, "_manifests", "tags")
|
||||||
|
if not os.path.isdir(tags_dir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get tags with their modification times from filesystem
|
||||||
|
tag_times = []
|
||||||
|
for tag in os.listdir(tags_dir):
|
||||||
|
tag_path = os.path.join(tags_dir, tag)
|
||||||
|
if os.path.isdir(tag_path):
|
||||||
|
mtime = os.path.getmtime(tag_path)
|
||||||
|
tag_times.append((mtime, tag))
|
||||||
|
|
||||||
|
if len(tag_times) <= KEEP:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Sort by mtime descending (newest first), delete everything past KEEP
|
||||||
|
tag_times.sort(reverse=True)
|
||||||
|
to_delete = tag_times[KEEP:]
|
||||||
|
|
||||||
|
print(f"[{repo}] has {len(tag_times)} tags, deleting {len(to_delete)}, keeping {KEEP}")
|
||||||
|
|
||||||
|
for _, tag in to_delete:
|
||||||
|
headers_resp = api(f"/v2/{repo}/manifests/{tag}", method="HEAD", headers={
|
||||||
|
"Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
||||||
|
})
|
||||||
|
if not headers_resp:
|
||||||
|
continue
|
||||||
|
digest = headers_resp.get("Docker-Content-Digest") or headers_resp.get("docker-content-digest")
|
||||||
|
if digest:
|
||||||
|
result = api(f"/v2/{repo}/manifests/{digest}", method="DELETE")
|
||||||
|
total_deleted += 1
|
||||||
|
|
||||||
|
print(f" deleted {len(to_delete)} tags")
|
||||||
|
|
||||||
|
print(f"\nDone. Deleted {total_deleted} total tags. Run garbage-collect to reclaim disk space.")
|
||||||
|
|
@ -151,6 +151,20 @@ module "proxmox" {
|
||||||
rybbit_site_id = "190a7ad3e1c7"
|
rybbit_site_id = "190a7ad3e1c7"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# https://registry.viktorbarzin.me/
|
||||||
|
module "docker-registry-ui" {
|
||||||
|
source = "./factory"
|
||||||
|
name = "registry"
|
||||||
|
external_name = "docker-registry.viktorbarzin.lan"
|
||||||
|
port = 8080
|
||||||
|
tls_secret_name = var.tls_secret_name
|
||||||
|
depends_on = [kubernetes_namespace.reverse-proxy]
|
||||||
|
extra_annotations = {
|
||||||
|
# Override middleware chain to remove rate-limit; the UI fires many API calls to list repos/tags
|
||||||
|
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,traefik-authentik-forward-auth@kubernetescrd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# https://valchedrym.viktorbarzin.me/
|
# https://valchedrym.viktorbarzin.me/
|
||||||
module "valchedrym" {
|
module "valchedrym" {
|
||||||
source = "./factory"
|
source = "./factory"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue