如何从私有Docker注册表中删除镜像?

273

我运行一个私有的Docker仓库,我希望从一个仓库中删除除latest以外的所有镜像。我不想删除整个仓库,只想删除其中的一些镜像。 API文档没有提到这种方法,但肯定是可以实现的吧?


7
已接受的答案不再正确(尽管肯定非常好)。您可以使用DELETE /v2/<name>/manifests/<reference>删除图像:https://docs.docker.com/registry/spec/api/#deleting-an-image。 - Michael Zelensky
@MichaelZelensky,我尝试使用“标签”来引用<reference>,但是这对我没有起作用。我应该使用哪个<reference>,或者如何获取它?(使用此注册表:https://registry.hub.docker.com/_/registry/ 从2020年10月开始) - Houtman
1
使用以下命令: GET -m DELETE -C <user/pw> https://<a remote host>:81/v2/<my-repo>/manifests/latest 响应: {"errors":[{"code":"UNSUPPORTED","message":"该操作不受支持。"}]} - Houtman
1
@Houtman,请看这里:https://dev59.com/_rvoa4cB1Zd3GeqP3m0F - Michael Zelensky
@MichaelZelensky,那个链接并没有解释不支持的错误,对吗?如果是的话,你能否总结一下呢? - Melardev
2
注意,只能使用摘要删除清单。 sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b 并且需要在注册表中设置以下环境变量才能进行删除: REGISTRY_STORAGE_DELETE_ENABLED=TRUE - Dek4nice
17个回答

142

目前您无法使用注册表 API来完成这个任务。它只允许您删除仓库或特定标签。

通常情况下,删除一个仓库意味着与该仓库关联的所有标签都将被删除。

删除标签意味着删除图像和标签之间的关联。

上述任何操作都不会删除单个图像。它们仍然保存在您的磁盘上。


解决方法

此解决方法需要您将 Docker 镜像存储在本地。

您可以通过删除除最新标签以外的所有标签来实现解决方案的变通方法,并可能因此删除与其相关联的图像引用。然后,您可以运行此脚本来删除未被任何标签引用或任何已使用图像的祖先引用的所有图像。

术语(图像和标签)

考虑一个图像图形,其中大写字母(AB 等)表示短的图像 ID,<- 表示一个图像基于另一个图像:

 A <- B <- C <- D

现在我们给图片添加标签:

 A <- B <- C <- D
           |    |
           |    <version2>
           <version1>

在这里,标签<version1>引用了图像C,而标签<version2>引用了图像D

精化您的问题

在您的问题中,您说您想要删除除“latest”之外的所有图像。现在,这个术语并不完全正确。您混淆了图像和标签。从图表上看,我认为您会同意标签<version2>代表最新版本。实际上,根据这个问题的说法,您可以拥有一个表示最新版本的标签:

 A <- B <- C <- D
           |    |
           |    <version2>
           |    <latest>
           <version1>

由于<latest>标签引用了图像D,我要问您:您真的想删除除图像D之外的所有内容吗?可能不是!

如果删除标签会发生什么?

如果您使用Docker REST API删除<version1>标签,则会获得以下结果:

 A <- B <- C <- D
                |
                <version2>
                <latest>

注意: Docker 永远不会删除镜像!即使它这样做了,在这种情况下也无法删除镜像,因为镜像 C 是标记为 D 的祖先的一部分。

即使使用了 此脚本 ,也不会删除任何镜像。

何时可以删除镜像

在你能够控制其他人何时从你的注册表中提取或推入镜像的情况下(例如通过禁用 REST 接口),如果没有其他镜像基于它并且没有标签引用它,则可以从镜像图中删除镜像。

请注意,在以下图表中,镜像 D 不是基于 C 而是基于 B。 因此,D 不依赖于 C。 如果您在此图表中删除标记 <version1>,则镜像 C 将不被任何镜像使用,此时此脚本 可以删除它。

 A <- B <--------- D
      \            |
       \           <version2>
        \          <latest>
         \ <- C
              |
              <version1>

清理后,您的图像图表如下所示:

 A <- B <- D
           |
           <version2>
           <latest>

这是你想要的吗?


你说得对——删除标签并没有实际删除相关联的图片。如果有SSH访问该服务器的权限,那么有没有办法删除这些图片呢? - Leo
2
我已经编辑了我的答案,并提供了一个解决方法,对我来说很有效。希望能对你有所帮助。 - Konrad Kleine
13
垃圾回收终于在Registry v2.4.0中实现了。https://docs.docker.com/registry/garbage-collection/ - Markus Rexhepi-Lindberg
我检查了你的脚本...为什么要经历这些麻烦呢?我知道标签的uuid值,也知道仓库名称。我还知道我的镜像没有祖先(除了基础的ubuntu)。所以我可以直接登录并且“rm -rf”相应的文件夹。顺便说一下,/var/lib/*不总是该位置,因此该脚本感觉有风险。 - daparic
@daixtr 我知道这个脚本并不完美,但它可以让你有个开头。如果你了解图像来源的情况,你可以随意删除其中任何部分,但要采用更系统化的方法,你需要经历所有的麻烦,或者像markus-lindberg指出的那样升级到至少2.4.0版本的注册表。 - Konrad Kleine
显示剩余5条评论

137
我曾经遇到过与我的注册表相同的问题,然后我尝试了下面博客页面列出的解决方案。它有效。
请注意,删除必须启用才能生效。您可以通过提供自定义配置或设置REGISTRY_STORAGE_DELETE_ENABLED=true来实现。
步骤1:列出存储库
$ curl -sS <domain-on-ip>:5000/v2/_catalog

响应将以以下格式返回:
{
  "repositories": [
    <repo>,
    ...
  ]
}

第二步:列出存储库标签
$ curl -sS <domain-on-ip>:5000/v2/<repo>/tags/list

响应将以以下格式返回:
{
    "name": <repo>,
    "tags": [
        <tag>,
        ...
    ]
}

第三步:确定目标标签的摘要
$ curl -sS -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
-o /dev/null \
-w '%header{Docker-Content-Digest}' \
<domain-or-ip>:5000/v2/<repo>/manifests/<tag>

请注意,在这里需要使用Accept头。如果没有它,你将得到一个不同的值,删除操作将失败。
请注意,-w选项仅在CURL v7.83.0及以上版本中支持。在较旧的CURL版本中,您需要使用其他工具(如sed、grep或awk)从输出中提取头内容。
响应将以以下格式返回:
sha256:6de813fb93debd551ea6781e90b02f1f93efab9d882a6cd06bbd96a07188b073

第四步:删除清单
$ curl -sS -X DELETE <domain-or-ip>:5000/v2/<repo>/manifests/<digest>

步骤5:垃圾回收图像

在您的Docker注册表容器中运行以下命令:

$ registry garbage-collect /etc/docker/registry/config.yml

这是我的config.yml文件:
version: 0.1
log:
    fields:
    service: registry
storage:
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    delete:
        enabled: true
http:
    addr: :5000
    headers:
        X-Content-Type-Options: [nosniff]
health:
    storagedriver:
        enabled: true
        interval: 10s
        threshold: 3

2
命令执行成功,包括关于“删除 blob: /docker/...”的消息,但它并没有改变磁盘空间的使用。使用 _bin/registry github.com/docker/distribution v2.4.1_。 - JamesThomasMoon
7
要删除一张图片,你需要在运行注册表容器时加上参数 REGISTRY_STORAGE_DELETE_ENABLED=true。 - Adil
3
我在/etc/docker/registry/config.yml文件中将 "delete: enabled" 的值设置为 true。对于这个配置,不需要设置 REGISTRY_STORAGE_DELETE_ENABLED 变量。@AdilIlhan - Yavuz Sert
3
谢谢,这是第一个对我有用的答案。我缺少了 Accept: application/vnd.docker.distribution.manifest.v2+json 头部信息,一直收到 MANIFEST_UNKNOWN 错误。尽管在他们的文档(https://docs.docker.com/registry/spec/api/)中有提到,但仍然很令人困惑。 - Tomasz W
2
步骤3没有返回任何输出(甚至没有错误!!),有什么建议! - McLan
显示剩余4条评论

91
当前的v2仓库支持通过DELETE /v2/<name>/manifests/<reference>进行删除操作。
参考文献:https://github.com/docker/distribution/blob/master/docs/spec/api.md#deleting-an-image 可以从GET /v2/<name>/manifests/<tag>请求的Docker-Content-Digest头获取<reference>(请注意,此请求需要Accept:application/vnd.docker.distribution.manifest.v2+json头)。
使用它的脚本:https://github.com/byrnedo/docker-reg-tool 为使其工作,必须启用删除功能(REGISTRY_STORAGE_DELETE_ENABLED=true)。
要真正释放磁盘空间,您需要运行垃圾回收

3
这里有一些其他的回答展示了如何提取清单引用哈希。这是将标记转换为传递给“DELETE”的内容所必需的。 - JamesThomasMoon
v2. 我的磁盘映像大小已经增长到65GB,主要是由于删除的注册表容器所贡献的,最终我不得不恢复到出厂设置。 - Winster
1
@JamesThomasMoon1979已更新有关获取参考哈希的说明。 - byrnedo
17
在V2上也遇到了错误{"errors":[{"code":"UNSUPPORTED","message":"The operation is unsupported."}]} - Gadelkareem
1
{"errors":[{"code":"UNSUPPORTED","message":"操作不支持。"}]} 有任何建议吗? - Kumaresan P
显示剩余2条评论

22

这真的很丑,但它有效。文本已在registry:2.5.1上进行测试。 即使更新配置以启用删除,我仍然无法使删除平稳运行。获取ID非常困难,必须登录才能获得,可能存在一些误解。无论如何,以下方法有效:

  1. 进入容器

 docker exec -it registry sh
  • 定义变量以匹配您的容器和容器版本:

  •  export NAME="google/cadvisor"
     export VERSION="v0.24.1"
    
  • 进入注册表目录:

     cd /var/lib/registry/docker/registry/v2
    
  • 删除与您的哈希相关的文件:

  •  find . | grep `ls ./repositories/$NAME/_manifests/tags/$VERSION/index/sha256`| xargs rm -rf $1
    
  • 删除清单:

     rm -rf ./repositories/$NAME/_manifests/tags/$VERSION
    
  • 退出登录

  •  exit
    
  • 运行垃圾回收器:

     docker exec -it registry  bin/registry garbage-collect  /etc/docker/registry/config.yml
    
  • 如果所有操作都正确执行,将会显示关于删除的Blobs的一些信息。


  • 我按照上述相同的步骤进行了操作,但是当我检查Docker注册表{container}的磁盘使用情况时,它显示的与在执行这些步骤之前相同... 有什么想法吗? - Savio Mathew
    无法工作。很容易检查。当您执行以下步骤并再次推送存储库时,它会显示“064794e955a6:层已存在”。 - Oduvan
    这确实很丑陋。而且现在有更好的方法。 - x-yuri

    20

    问题1

    您提到这是您的私有Docker注册表,因此您可能需要检查Registry API而不是您提供的Hub registry API doc链接。

    问题2

    Docker注册表API是客户端/服务器协议,由服务器的实现决定是否删除后端中的映像。(我猜)

    DELETE /v1/repositories/(namespace)/(repository)/tags/(tag*)
    

    详细解释

    根据您的描述,我将演示它是如何工作的。

    我运行一个私有的Docker仓库。 我使用默认设置,并监听端口5000

    docker run -d -p 5000:5000 registry
    

    然后我标记本地图像并将其推送。
    $ docker tag ubuntu localhost:5000/ubuntu
    $ docker push localhost:5000/ubuntu
    The push refers to a repository [localhost:5000/ubuntu] (len: 1)
    Sending image list
    Pushing repository localhost:5000/ubuntu (1 tags)
    511136ea3c5a: Image successfully pushed
    d7ac5e4f1812: Image successfully pushed
    2f4b4d6a4a06: Image successfully pushed
    83ff768040a0: Image successfully pushed
    6c37f792ddac: Image successfully pushed
    e54ca5efa2e9: Image successfully pushed
    Pushing tag for rev [e54ca5efa2e9] on {http://localhost:5000/v1/repositories/ubuntu/tags/latest}
    

    之后我可以使用注册表 API来检查它是否存在于您的私有 Docker 注册表中。

    $ curl -X GET localhost:5000/v1/repositories/ubuntu/tags
    {"latest": "e54ca5efa2e962582a223ca9810f7f1b62ea9b5c3975d14a5da79d3bf6020f37"}
    

    现在我可以使用那个API删除标签!!
    $ curl -X DELETE localhost:5000/v1/repositories/ubuntu/tags/latest
    true
    

    请再检查一遍,该标签在我的私有注册服务器中不存在。

    $ curl -X GET localhost:5000/v1/repositories/ubuntu/tags/latest
    {"error": "Tag not found"}
    

    5
    请看下面我的回答,解释为什么这个命令没有帮助。 - Konrad Kleine
    3
    您删除的是标签,而非图像数据。在您删除标签后,我会使用参考的脚本执行后者的操作。我还使用注册表 API 删除标签。 - Konrad Kleine
    3
    从协议的角度来看,每个注册表API服务器的实现都不同,因此不存在标准规定。 - Larry Cai
    您是正确的,这取决于实现方式——但我正在寻找一种从规范的“注册表”图像中删除图像数据的方法。即使不理想,通过SSH登录并运行脚本也可以解决问题。另外,我仍然认为这是一个不完整的API——您可以删除标签,但如果您GET“/images”,您仍然会看到所有剩余的图像数据。 - Leo
    这个使用'curl'方法删除标签非常有帮助。感谢您,先生。 - daparic
    显示剩余5条评论

    16

    有一些客户端(如Python、Ruby等)可以做到这一点。但我认为,在我的注册表服务器上安装运行时(例如Python)仅仅是为了维护我的注册表并不可持续!


    因此,deckschrubber 是我的解决方案:

    go install github.com/fraunhoferfokus/deckschrubber@latest
    $GOPATH/bin/deckschrubber
    

    自动删除指定年龄之前的图片。可以使用 -year-month-day 或它们的组合来指定图片的年龄。

    $GOPATH/bin/deckschrubber -month 2 -day 13 -registry http://registry:5000
    

    更新:这里是关于deckschrubber的简短介绍:https://blog.quaintous.com/2017/05/19/docker-registry-housekeeping/


    1
    deckschrubber非常好用 - 安装非常简单(单个二进制文件),可以按名称(使用正则表达式匹配)和年龄删除图像。 - RichVel
    ERRO[0000] 无法删除镜像!repo=.... tag=latest :/ - scythargon
    @scythargon 不可能确定你的规格说明是否正确。 - jitter
    不幸的是,它将无法删除没有标签的图像。因此,如果您在单个标签上不断推送图像,则仅会检查已标记的标签,而不是其他标签。 - Krystian
    @YanFoto 这是一个问题吗?我认为这就是预期的使用方式。 - Krystian
    显示剩余3条评论

    7
    要求删除除了latest之外的所有标签变得复杂,因为同一个镜像清单可以被多个标签指向,所以当你删除一个标签的清单时,可能会有效地删除多个标签。
    有几种可行的选择。一种是跟踪最新标签的摘要,并仅删除其他摘要的清单,或者您可以使用一些不同的API调用来删除标签本身。
    无论您如何实施,首先需要配置您的注册表以允许删除API。对于最小化的registry:2镜像,这涉及使用环境变量REGISTRY_STORAGE_DELETE_ENABLED=true(或等效的yaml配置)启动它。
    然后,对于一个简单的循环遍历标签并删除的脚本,可以使用以下代码:
    #!/bin/sh
    repo="localhost:5000/repo/to/prune"
    for tag in $(regctl tag ls $repo); do
      if [ "$tag" != "latest" ]; then
        echo "Deleting: $(regctl image digest --list "${repo}:${tag}") [$tag]"
        regctl tag rm "${repo}:${tag}"
      fi
    done
    

    这里使用的regctl命令来自regclientregctl tag rm逻辑首先尝试执行最近添加到distribution-spec的标签删除API。由于大多数注册表尚未实现该规范,它会回退到清单删除API,但首先创建一个虚拟清单来覆盖标签,然后删除该新摘要。这样做的话,如果旧的清单被其他标签使用,它不会删除那些其他标签。
    删除除指向latest摘要之外的清单的替代版本脚本如下:
    #!/bin/sh
    repo="localhost:5000/repo/to/prune"
    save="$(regctl image digest --list "${repo}:latest")"
    for tag in $(regctl tag ls $repo); do
      digest="$(regctl image digest --list "${repo}:${tag}")"
      if [ "$digest" != "$save" ]; then
        echo "Deleting: $digest [$tag]"
        regctl manifest rm "${repo}@${digest}"
      fi
    done
    

    如果你发现自己需要创建一个删除策略来自动删除大量的图片,我建议你看一下同一仓库中的regclient/regbot,它允许你定义这个策略并让它持续运行以不断修剪你的注册表。
    一旦图像被删除,你需要在大多数使用情况下执行垃圾回收以清理你的注册表。例如,对于看起来像这样的registry:2图像:
    docker exec registry /bin/registry garbage-collect \
      /etc/docker/registry/config.yml --delete-untagged
    

    注意:垃圾收集工具存在一个已知问题,会删除多平台镜像的未打标签的子清单。如果您正在使用多平台镜像,请确保每个特定平台的镜像都已经打上标签,或者在问题在您部署的版本中得到解决之前避免使用上述GC命令。


    3

    我通常支持使用脚本来完成任务,但如果您已经在运行从Joxit/docker-registry-ui构建的注册表UI容器,则最好直接在UI中选择单击删除按钮,并一次删除一页图像,然后进行垃圾回收。


    2

    您可以使用的另一个工具是registry-cli。例如,该命令:

    registry.py -l "login:password" -r https://your-registry.example.com --delete
    

    将删除除最后10张图像以外的所有图像。


    2

    您还可以根据创建日期从代码库中删除一些旧的镜像。

    为此,请进入您的Docker注册表容器,并获取某个特定代码库的清单修订列表:

    ls -latr /var/lib/registry/docker/registry/v2/repositories/YOUR_REPO/_manifests/revisions/sha256/
    

    输出结果可以在请求中使用(带有sha256前缀):
    curl -v --silent -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -X DELETE http://DOCKER_REGISTRY_HOST:5000/v2/YOUR_REPO/manifests/sha256:OUTPUT_LINE
    

    当然,在此之后不要忘记执行“garbage-collect”命令:
    bin/registry garbage-collect /etc/docker/registry/config.yml
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接