apiVersion: batch/v1 kind: CronJob metadata: name: registry-gc namespace: registry spec: # Run daily at 3am schedule: "0 3 * * *" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: registry-gc image: registry:2 command: - /bin/sh - -c - | set -e echo "Starting registry garbage collection..." # Keep the last 10 tags per repo; delete the rest via registry API REGISTRY_URL="http://registry:5000" # Get all repositories REPOS=$(wget -qO- "$REGISTRY_URL/v2/_catalog" | sed 's/.*"repositories":\[//;s/\].*//' | tr ',' '\n' | tr -d '"') for repo in $REPOS; do echo "Processing repo: $repo" # Get all tags sorted by date (registry API returns unordered, use digest order) TAGS=$(wget -qO- "$REGISTRY_URL/v2/$repo/tags/list" | sed 's/.*"tags":\[//;s/\].*//' | tr ',' '\n' | tr -d '"') TAG_COUNT=$(echo "$TAGS" | grep -c . || true) echo " $TAG_COUNT tags found" # Separate SHA tags (40-char hex) from named tags (latest, etc.) SHA_TAGS=$(echo "$TAGS" | grep -E '^[0-9a-f]{40}$' || true) SHA_COUNT=$(echo "$SHA_TAGS" | grep -c . || true) # Keep the 10 most recent SHA tags; delete older ones KEEP=10 if [ "$SHA_COUNT" -gt "$KEEP" ]; then TO_DELETE=$(echo "$SHA_TAGS" | head -n "-$KEEP") echo " Deleting $(echo "$TO_DELETE" | grep -c .) old SHA tags..." for tag in $TO_DELETE; do # Get the manifest digest DIGEST=$(wget -qO- --server-response \ --header="Accept: application/vnd.docker.distribution.manifest.v2+json" \ "$REGISTRY_URL/v2/$repo/manifests/$tag" 2>&1 \ | grep -i "docker-content-digest" | awk '{print $2}' | tr -d '\r') if [ -n "$DIGEST" ]; then # Delete by digest wget -q --method=DELETE \ "$REGISTRY_URL/v2/$repo/manifests/$DIGEST" && \ echo " Deleted $repo:$tag ($DIGEST)" || \ echo " Failed to delete $repo:$tag" fi done else echo " Skipping (only $SHA_COUNT SHA tags, keep threshold is $KEEP)" fi done # Run the built-in GC to reclaim disk space from deleted layers echo "Running storage garbage collection..." /bin/registry garbage-collect /etc/docker/registry/config.yml --delete-untagged=false echo "Done." volumeMounts: - name: data mountPath: /var/lib/registry - name: config mountPath: /etc/docker/registry/config.yml subPath: config.yml volumes: - name: data persistentVolumeClaim: claimName: registry-data - name: config configMap: name: registry-gc-config --- apiVersion: v1 kind: ConfigMap metadata: name: registry-gc-config namespace: registry data: config.yml: | version: 0.1 storage: filesystem: rootdirectory: /var/lib/registry delete: enabled: true http: addr: :5000