19

Given a Docker Registry at localhost:5000, how can I use the Docker Registry HTTP API V2 and curl to delete busybox:latest that has the following manifest header and manifest:

derek@derekmahar-ubuntu-512mb-tor1-01:~/Projects/docker-registry$ curl --head --request GET http://localhost:5000/v2/busybox/manifests/latest
HTTP/1.1 200 OK
Content-Length: 2561
Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws
Docker-Content-Digest: sha256:e45f25b1760f616e65f106b424f4ef29185fbd80822255d79dabc73b8eb715ad
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:e45f25b1760f616e65f106b424f4ef29185fbd80822255d79dabc73b8eb715ad"
X-Content-Type-Options: nosniff
Date: Wed, 04 May 2016 16:10:00 GMT

derek@derekmahar-ubuntu-512mb-tor1-01:~/Projects/docker-registry$ curl --request GET http://localhost:5000/v2/busybox/manifests/latest
{
   "schemaVersion": 1,
   "name": "busybox",
   "tag": "latest",
   "architecture": "amd64",
   "fsLayers": [
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:385e281300cc6d88bdd155e0931fbdfbb1801c2b0265340a40481ee2b733ae66"
      }
   ],
   "history": [
      {
         "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"156e10b83429\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"sh\"],\"Image\":\"56ed16bd6310cca65920c653a9bb22de6b235990dcaa1742ff839867aed730e5\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"container\":\"5f8098ec29947b5bea80483cd3275008911ce87438fed628e34ec0c522665510\",\"container_config\":{\"Hostname\":\"156e10b83429\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"sh\\\"]\"],\"Image\":\"56ed16bd6310cca65920c653a9bb22de6b235990dcaa1742ff839867aed730e5\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2016-03-18T18:22:48.810791943Z\",\"docker_version\":\"1.9.1\",\"id\":\"437595becdebaaaf3a4fc3db02c59a980f955dee825c153308c670610bb694e1\",\"os\":\"linux\",\"parent\":\"920777304d1d5e337bc59877253e946f224df5aae64c72538672eb74637b3c9e\"}"
      },
      {
         "v1Compatibility": "{\"id\":\"920777304d1d5e337bc59877253e946f224df5aae64c72538672eb74637b3c9e\",\"created\":\"2016-03-18T18:22:48.262403239Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:47ca6e777c36a4cfffe3f918b64a445c8f32300deeb9dfa5cc47261bd7b75d21 in /\"]}}"
      }
   ],
   "signatures": [
      {
         "header": {
            "jwk": {
               "crv": "P-256",
               "kid": "RVEO:MXL3:ZYLW:BFGB:QAKI:SIKZ:JOVR:U7AP:2LGY:4SSF:MSV4:LND7",
               "kty": "EC",
               "x": "ZFyhNUInMwlVmFKzz-e-o_tzMd01ZdCj6LyGV4dnT5Y",
               "y": "3a0BcWa0h60tTGHYhUI4ziKisRk6b4JtvqbmL9kKy6E"
            },
            "alg": "ES256"
         },
         "signature": "BVWioJafWmMrUBBi9meRy9CUQYdsZgwkY01ipT1HInXofVXCIZmNMq7EFEl4A88pMZsf7DHZS2dyFeFjj-QP1Q",
         "protected": "eyJmb3JtYXRMZW5ndGgiOjE5MTQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNi0wNS0wNFQxNjoxMzo0M1oifQ"
      }
   ]
}

Docker Registry complains with "HTTP/1.1 404 Not Found" and "MANIFEST_UNKNOWN" when I attempt to DELETE the image digest:

derek@derekmahar-ubuntu-512mb-tor1-01:~/Projects/docker-registry$ curl --request DELETE http://localhost:5000/v2/busybox/manifests/sha256:e45f25b1760f616e65f106b424f4ef29185fbd80822255d79dabc73b8eb715ad
{"errors":[{"code":"MANIFEST_UNKNOWN","message":"manifest unknown"}]}
Derek Mahar
  • 27,608
  • 43
  • 124
  • 174
  • Similar question, slightly different requirements: [remove docker repository on remote docker registry](http://stackoverflow.com/questions/37033055/how-can-i-use-the-docker-registry-api-v2-to-delete-an-image-from-a-private-regis) – JamesThomasMoon Dec 06 '18 at 21:45

3 Answers3

31

PLEASE NOTE: This API endpoint is only implemented in later versions of v2 registry!

There is an internal garbage collection process which deletes images in the registry, but which is not (yet) accessible via the v2 registry API. This feature looks like it has a lot of recent movement, so this information could change.

According to the API spec, you've got to grab the image digest in order to run a DELETE against it. Read the spec carefully and notice the part about the required header when asking for the right image digest ("Note When deleting a manifest from a registry version 2.3 or later, the following header must be used when HEAD or GET-ing the manifest to obtain the correct digest to delete: Accept: application/vnd.docker.distribution.manifest.v2+json")

curl -v -u "user@example.com:passw0rd" -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -X HEAD https://registry.example.com/v2/derek/busybox/manifests/latest

When you do that, you're going to be paying special attention to the Docker-Content-Digest header. In your particular request, that full header looks like this:

Docker-Content-Digest: sha256:e45f25b1760f616e65f106b424f4ef29185fbd80822255d79dabc73b8eb715ad

Now with that digest you should be able to plug it into the format described by the API you linked:

DELETE /v2/<name>/manifests/<reference>

And to use the example I used above, but with your digest, the curl looks like this:

curl -u "user@example.com:passw0rd" -X DELETE https://registry.example.com/v2/derek/busybox/manifests/sha256:e45f25b1760f616e65f106b424f4ef29185fbd80822255d79dabc73b8eb715ad

Then you should get a 202 Accepted response code indicating successful deletion.

You can also try the short script from this gist:

registry='localhost:5000'
name='my-image'
curl -v -sSL -X DELETE "http://${registry}/v2/${name}/manifests/$(
    curl -sSL -I \
        -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
        "http://${registry}/v2/${name}/manifests/$(
            curl -sSL "http://${registry}/v2/${name}/tags/list" | jq -r '.tags[0]'
        )" \
    | awk '$1 == "Docker-Content-Digest:" { print $2 }' \
    | tr -d $'\r' \
)"
L0j1k
  • 12,255
  • 7
  • 53
  • 65
  • Why must I specify my user name in the URL? – Derek Mahar May 05 '16 at 19:07
  • I appended to my question an example of an unsuccessful attempt to `DELETE` an image. Assuming that I've specified the request correctly, I'm puzzled why Docker Registry cannot find the image having the given digest. – Derek Mahar May 05 '16 at 19:50
  • Are you absolutely certain that your `busybox` image doesn't include a user namespace there, as in my example? I was only able to get it working by specifying the full name of the image, including the "repo" (username), e.g. `https://reg.foo.com/v2/l0j1k/busybox/manifests/sha:deadbeefbabecafe`. Only at that point was I able to get anything other than a 404. Also make sure you are using a high enough Distribution version number, e.g. at least Registry v2.4. – L0j1k May 05 '16 at 20:51
  • As my question shows, the `GET` request can find the image and reports its digest, but the `DELETE` request fails. – Derek Mahar May 05 '16 at 22:43
  • How can I query the version number of the registry? I pulled it very recently. – Derek Mahar May 05 '16 at 22:43
  • Does Registry *require* that an image belong to a user namespace? Docker can pull the `busybox` image from my registry, so my guess is that a user namespace is not required. – Derek Mahar May 05 '16 at 23:23
  • I couldn't delete from my registry without specifying the user/repo part of the image name. It let me *query* the manifest, etc, but didn't let me delete until I'd provided that. I'm not sure if it's required, and it's not even mentioned in the API, but that's how it worked for me. – L0j1k May 05 '16 at 23:24
  • Really I think the Distribution people dropped the ball on this API. It is not transparent at all. – L0j1k May 05 '16 at 23:25
  • Also if you're just looking to get rid of old images, there is a garbage collection utility now available in the newest versions of the registry. I am not able to determine the version of the registry alone from querying its API, so I don't think that's possible, but again, not sure. That GC utility has generated a lot of discussion on GH and may serve your purposes better than this delete business, but I can't help with more than telling you about it because I've never used it, only heard about it. – L0j1k May 05 '16 at 23:28
  • 3
    You need to specify the *Accept* header of the HEAD request correctly. Just edited the answer to reflect that. – Tim Jun 16 '16 at 07:26
  • @L0j1k I did what you recommended but it did not work for me, for more details http://stackoverflow.com/q/39918794/6638204 – Said Saifi Oct 07 '16 at 14:31
  • Answer worked for me. Of course if you also want to reclaim the disk space you have to run the garbage collection. This said, this API stinks. Why do we need that extra ID (there already a few to choose from in the manifests)? – xenoid Nov 09 '18 at 09:17
  • @L0j1k Is it possible to delete the images based on the time it is pushed to the repository? There is a created time in `history`. But which created should be taken for the image. – PGS Feb 11 '20 at 08:57
  • the Go library I was using does not have this and I was getting MANIFEST_UNKNOWN, had to write my custom code adding this header. Saved me a lot of time figuring out what's going on. Huge thanks!!! – edbighead Nov 03 '20 at 13:59
10

You may also need to enable deletion in the config file.

See https://docs.docker.com/registry/configuration/#/delete

4

I had this exact same problem. The MANIFEST_UNKNOWN error happens when using a valid digest, but not a digest that refers to the manifest. You MUST MUST MUST use '-H "Accept: application/vnd.docker.distribution.manifest.v2+json' to get the correct Docker-Content-Digest value that refers to manifest.

$ curl -s -I http://$REGISTRY/v2/myapp/manifests/1.0.0 | awk '/^Docker-Content-Digest/ {print $2}'
sha256:c1102294980e4ad2563a7ba65e9192ec3980dbb1f863fd1f81adcb95bc6e0c2a
$
$ curl -s -X DELETE http://$REGISTRY/v2/myapp/manifests/sha256:c1102294980e4ad2563a7ba65e9192ec3980dbb1f863fd1f81adcb95bc6e0c2a
{"errors":[{"code":"MANIFEST_UNKNOWN","message":"manifest unknown"}]}

Versus

$ curl -s -I -H "Accept: application/vnd.docker.distribution.manifest.v2+json" http://$REGISTRY/v2/myapp/manifests/1.0.0 | awk '/^Docker-Content-Digest/ {print $2}'
sha256:5403b52afdba954349746c60f4e6dc18d2a6ab414333c8ab4b0928126e63371f
$
$ curl -s -X DELETE http://$REGISTRY/v2/myapp/manifests/sha256:5403b52afdba954349746c60f4e6dc18d2a6ab414333c8ab4b0928126e63371f
$ # successfully deleted, no error