Working with Docker Registry v2.6.0+, you'll encounter scenarios where you need to prune specific tags while preserving others that might reference the same image layers. The registry API doesn't provide a direct "delete tag" endpoint, which leads to this common workflow:
DELETE /v2/<name>/manifests/<reference>
But this deletes the manifest, not just the tag. Here's how to handle it properly.
Docker Registry v2 stores tags as manifest references. When multiple tags point to the same image (like foo/bar:1
and foo/bar:1_keep
in your case), they share the same manifest digest.
First, authenticate with your registry:
TOKEN=$(curl -s -u username:password "https://repo/auth?service=registry.docker.io&scope=repository:foo/bar:pull,push" | jq -r '.token')
Get the manifest digest for your target tag:
DIGEST=$(curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://repo/v2/foo/bar/manifests/1" | jq -r '.config.digest')
Before deletion, verify if other tags reference the same manifest:
ALL_TAGS=$(curl -s -H "Authorization: Bearer $TOKEN" "https://repo/v2/foo/bar/tags/list" | jq -r '.tags[]')
for tag in $ALL_TAGS; do
tag_digest=$(curl -s -I -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://repo/v2/foo/bar/manifests/$tag" | grep Docker-Content-Digest | cut -d ' ' -f 2 | tr -d '\r')
if [ "$tag_digest" == "$DIGEST" ]; then
echo "$tag references the same manifest"
fi
done
Option 1: Using Registry API (when no other tags exist):
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://repo/v2/foo/bar/manifests/$DIGEST"
Option 2: For shared manifests (multiple tags):
You'll need to re-tag the image before deleting:
docker pull repo/foo/bar:1_keep
docker tag repo/foo/bar:1_keep repo/foo/bar:new_version
docker push repo/foo/bar:new_version
# Now safe to delete original manifest
Consider these specialized tools for complex scenarios:
registry-cli
(Python-based registry client)skopeo
(Red Hat's container tool)regclient
(Modern registry client)
The registry maintains data consistency through content-addressable storage. Tags are metadata pointers to manifests, not independent entities. This architectural choice ensures layer sharing and prevents data corruption.
When working with Docker registries (especially private ones), you'll often encounter situations where you need to remove specific tags while preserving the underlying image layers. The current Docker Registry API (v2) doesn't provide a direct endpoint for tag-only deletion, which leads to this common frustration.
The Docker Registry v2 API expects you to delete manifests rather than tags. A simple DELETE /v2/<name>/manifests/<reference>
request will remove the manifest, but this affects all tags pointing to that manifest.
# What you WANT to do (but doesn't work):
DELETE /v2/foo/bar/tags/1
# What you HAVE to do instead:
DELETE /v2/foo/bar/manifests/sha256:abc123
Here's a practical approach using the Registry API and some shell scripting:
#!/bin/bash
REGISTRY="your.registry.com"
IMAGE="foo/bar"
TAG_TO_REMOVE="1"
# Step 1: Get manifest digest for the tag
MANIFEST=$(curl -sI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://${REGISTRY}/v2/${IMAGE}/manifests/${TAG_TO_REMOVE}" \
| grep -i 'Docker-Content-Digest' | cut -d ' ' -f 2 | tr -d '\r')
# Step 2: Count references to this manifest
REF_COUNT=$(curl -s "https://${REGISTRY}/v2/${IMAGE}/tags/list" \
| jq -r '.tags[]' \
| xargs -I {} curl -sI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://${REGISTRY}/v2/${IMAGE}/manifests/{}" \
| grep -c "$MANIFEST")
# Step 3: Only delete if no other references exist
if [ "$REF_COUNT" -eq 1 ]; then
curl -X DELETE "https://${REGISTRY}/v2/${IMAGE}/manifests/${MANIFEST}"
else
echo "Manifest is referenced by other tags - skipping deletion"
fi
For regular maintenance, consider these specialized tools:
- regclient: Provides tag-specific deletion capabilities
- docker-distribution-pruner: Built for safe registry cleanup
- Portus: Web UI with tag management features
The Docker registry treats tags as mutable pointers to immutable manifests. This design:
- Ensures content-addressable storage
- Maintains layer sharing between images
- Provides cryptographic verification of images
When implementing this in production:
- Always test in a staging environment first
- Implement proper authentication (JWT tokens)
- Consider garbage collection implications
- Monitor registry storage after deletions
For large registries, you might want to implement a scheduled cleanup job that:
# Sample cron job (runs weekly)
0 3 * * 0 /path/to/cleanup-script.sh >> /var/log/registry-cleanup.log