随机
Enter 搜索 ↑↓ 切换 Esc 清空

docker-hub_images_delete

脚本

Docker Hub 镜像标签删除工具

docker-hub_images_delete

Docker Hub 镜像标签删除工具

一键脚本

bash <(curl -sL gitee.com/meimolihan/cmdbox/raw/master/sh/docker-hub_images_delete.sh) mobufan:dckr_pat_xxxxxx mobufan/metube-zh
bash <(curl -sL ../sh/docker-hub_images_delete.sh) mobufan:dckr_pat_xxxxxx mobufan/metube-zh
模式 命令 说明
交互模式 bash docker-hub_images_delete.sh 交互式输入 PAT、仓库名、标签名
传参模式 bash docker-hub_images_delete.sh <用户名:PAT密钥> <仓库名> 传入 PAT 和仓库名,跳过对应输入步骤

效果预览

执行脚本效果预览

补充说明

该脚本用于通过 Docker Hub 官方 Registry API 删除指定镜像仓库中的标签(tag)。支持使用 Personal Access Token(推荐)或 用户名:密码 进行认证,自动获取镜像摘要(digest)后发送删除请求。

生成个人令牌

功能特点

工作流程

输入认证信息(PAT 或 用户名:密码)
输入目标仓库名(如 mobufan/cmdbox)
获取 Registry API Token
获取仓库所有标签列表
用户输入要删除的标签名
获取标签对应的镜像摘要(digest)
发送 DELETE 请求删除标签
显示删除结果并更新标签列表
# 定义账号、个人访问令牌、仓库名、待操作镜像标签
USER="mobufan"
PAT="dckr_pat_xxxxxx"
REPO="mobufan/cmdbox"
TAG="2026.06.03"

# 拉取 mobufan/cmdbox 仓库下所有标签(单次最多获取100条),仅输出标签名称
curl -s "https://hub.docker.com/v2/repositories/mobufan/cmdbox/tags?page_size=100" | jq -r '.results[].name'

# 根据仓库+标签查询镜像摘要(digest),赋值给变量
DIGEST=$(curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" | jq -r '.digest')
# 打印获取到的镜像摘要
echo $DIGEST

# 通过 Digest 删除 Docker 镜像清单(需传入认证令牌)
curl -X DELETE -i -H "Authorization: Bearer ${AUTH_TOKEN}" "https://registry-1.docker.io/v2/${REPO}/manifests/${DIGEST}"

认证方式说明

认证方式 格式 说明
Personal Access Token dckr_pat_xxxxxx 推荐方式,需在 Docker Hub 账户设置中生成
用户名 + 密码 username:password 使用 Docker Hub 登录密码

如何生成 PAT:登录 Docker Hub → Account Settings → Personal Access Tokens → Generate Token

参数说明

参数位置 说明 默认值
第 1 个参数(可选) Docker Hub PAT 或 用户名:密码 交互式输入
第 2 个参数(可选) 仓库名(格式:用户名/仓库名 交互式输入,默认 mobufan/cmdbox

输出说明

阶段 输出内容
初始化 检查并安装 curljq 依赖
认证 显示当前操作用户和仓库名
获取标签 仓库所有标签列表
删除操作 目标标签的镜像摘要(digest)及删除结果
完成 删除后仓库最新标签列表

注意事项

脚本源码

#!/bin/bash

list_color_init() {
    export gl_hui=$'\033[38;5;59m'
    export gl_hong=$'\033[38;5;9m'
    export gl_lv=$'\033[38;5;10m'
    export gl_huang=$'\033[38;5;11m'
    export gl_lan=$'\033[38;5;32m'
    export gl_bai=$'\033[38;5;15m'
    export gl_zi=$'\033[38;5;13m'
    export gl_bufan=$'\033[38;5;14m'
    export reset=$'\033[0m'
}
list_color_init

log_info()  { echo -e "${gl_lan}[信息]${gl_bai} $*"; }
log_ok()    { echo -e "${gl_lv}[成功]${gl_bai} $*"; }
log_warn()  { echo -e "${gl_huang}[警告]${gl_bai} $*"; }
log_error() { echo -e "${gl_hong}[错误]${gl_bai} $*" >&2; }

sleep_fractional() {
    local seconds=$1
    if sleep "$seconds" 2>/dev/null; then return 0; fi
    if command -v perl >/dev/null 2>&1; then perl -e "select(undef, undef, undef, $seconds)"; return 0; fi
    if command -v python3 >/dev/null 2>&1; then python3 -c "import time; time.sleep($seconds)"; return 0; fi
    if command -v python >/dev/null 2>&1; then python -c "import time; time.sleep($seconds)"; return 0; fi
    local int_seconds=$(echo "$seconds" | awk '{print int($1+0.999)}')
    sleep "$int_seconds"
}

exit_script() {
    echo ""
    echo -ne "${gl_hong}感谢使用,再见!${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.5
    echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.6
    clear
    exit 0
}

handle_invalid_input() {
    echo -ne "\r\033[K${gl_huang}无效的输入,请重新输入! ${gl_zi} 1 ${gl_huang} 秒后返回"
    sleep_fractional 1
    echo -ne "\r\033[K${gl_lv}无效的输入,请重新输入! ${gl_zi}0${gl_lv} 秒后返回"
    sleep_fractional 0.5
    echo -ne "\r\033[K"
    return 2
}

handle_y_n() {
    echo -e "${gl_hong}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}${gl_hong}N${gl_bai})${gl_hong}${gl_bai}"
    sleep 1
    echo -e "${gl_huang}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}${gl_hong}N${gl_bai})${gl_huang}${gl_bai}"
    sleep 1
    echo -e "${gl_lv}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}${gl_hong}N${gl_bai})${gl_lv}${gl_bai}"
    sleep 0.5
    return 2
}

break_end() {
    echo -e "${gl_lv}操作完成${gl_bai}"
    echo -e "${gl_bai}按任意键继续 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
    read -r -n 1 -s -r -p ""
    echo ""
    clear
}

install() {
    [[ $# -eq 0 ]] && {
        log_error "未提供软件包参数!"
        return 1
    }

    local pkg mgr ver installed
    
    for pkg in "$@"; do
        installed=false
        ver=""
        
        case "$pkg" in
            7zip|7z)
                if command -v 7z &>/dev/null; then
                    ver=$(7z 2>&1 | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1)
                    [[ -n "$ver" ]] && installed=true
                fi
                ;;
            *)
                if command -v "$pkg" &>/dev/null; then
                    ver=$("$pkg" --version 2>/dev/null | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1)
                    [[ -n "$ver" ]] && installed=true
                fi
                ;;
        esac
        
        if [[ "$installed" == false ]] && command -v opkg &>/dev/null; then
            if opkg list-installed 2>/dev/null | grep -q "^${pkg} "; then
                installed=true
                ver=$(opkg list-installed 2>/dev/null | grep "^${pkg} " | awk '{print $3}')
            fi
        fi
        
        if [[ "$installed" == true ]]; then
            echo -e "${gl_huang}${pkg}${gl_bai} ${gl_lv}已安装${gl_bai}${ver:+ 版本 ${gl_lv}${ver}${gl_bai}}"
            continue
        fi
        
        echo -e "\n${gl_huang}开始安装:${gl_bai}${pkg}"
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
        
        local install_success=false
        
        for mgr in opkg dnf yum apt apk pacman zypper pkg; do
            command -v "$mgr" &>/dev/null || continue
            
            case $mgr in
                opkg)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}opkg (OpenWrt/iStoreOS)${gl_bai}"
                    opkg update && {
                        if [[ "$pkg" == "7zip" || "$pkg" == "7z" ]]; then
                            opkg install p7zip && install_success=true
                        else
                            opkg install "$pkg" && install_success=true
                        fi
                    }
                    ;;
                dnf)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}dnf (Fedora/RHEL)${gl_bai}"
                    dnf install -y "$pkg" && install_success=true
                    ;;
                yum)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}yum (CentOS/RHEL)${gl_bai}"
                    yum install -y "$pkg" && install_success=true
                    ;;
                apt)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}apt (Debian/Ubuntu)${gl_bai}"
                    apt update -y && apt install -y "$pkg" && install_success=true
                    ;;
                apk)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}apk (Alpine)${gl_bai}"
                    apk add "$pkg" && install_success=true
                    ;;
                pacman)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}pacman (Arch/Manjaro)${gl_bai}"
                    pacman -S --noconfirm "$pkg" && install_success=true
                    ;;
                zypper)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}zypper (openSUSE)${gl_bai}"
                    zypper install -y "$pkg" && install_success=true
                    ;;
                pkg)
                    echo -e "${gl_bai}使用包管理器: ${gl_zi}pkg (FreeBSD)${gl_bai}"
                    pkg install -y "$pkg" && install_success=true
                    ;;
            esac
            
            [[ "$install_success" == true ]] && break
        done
        
        if [[ "$install_success" == true ]]; then
            echo -e "${gl_lv}${pkg} 安装成功${gl_bai}"
        else
            echo -e "${gl_hong}${pkg} 安装失败${gl_bai}"
        fi
        
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    done
}

get_docker_token() {
    local repo=$1
    local pat=$2
    local token
    
    token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull,push,delete" \
        -H "Authorization: Bearer ${pat}" | jq -r '.token')
    
    if [[ -z "$token" || "$token" == "null" ]]; then
        token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull,push,delete" \
            -u "${pat}" | jq -r '.token')
    fi
    
    echo "$token"
}

main() {
    install curl jq

    local INPUT_PAT="$1"
    local INPUT_REPO="$2"
    local PAT REPO USER TOKEN

    clear
    echo -e "${gl_zi}>>> Docker Hub 镜像标签删除工具${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if [[ -z "${INPUT_PAT}" ]]; then
        read -r -e -p "$(echo -e "${gl_bai}请输入 Docker Hub PAT 或 用户名:密码(格式: username:password) (${gl_hong}0${gl_bai}退出): ")" PAT
        [[ "${PAT}" = "0" ]] && exit_script
    else
        PAT="${INPUT_PAT}"
        log_info "已使用传入的 PAT"
    fi

    if [[ -z "${INPUT_REPO}" ]]; then
        read -r -e -p "$(echo -e "${gl_bai}请输入仓库名称(格式:用户名/仓库名 ${gl_hong}0${gl_bai}退出,默认:mobufan/cmdbox): ")" REPO
        [[ "${REPO}" = "0" ]] && exit_script
        [[ -z "${REPO}" ]] && REPO="mobufan/cmdbox"
    else
        REPO="${INPUT_REPO}"
        log_info "已使用传入的仓库: ${REPO}"
    fi

    USER="${REPO%%/*}"
    echo -e "${gl_bai}当前操作用户: ${gl_lv}${USER}${gl_bai},仓库: ${gl_huang}${REPO}${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    log_info "正在获取认证 Token ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
    TOKEN=$(get_docker_token "$REPO" "$PAT")
    
    if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
        log_error "认证失败!请检查 PAT 或用户名密码是否正确"
        exit 1
    fi
    log_ok "认证成功"

    echo -e ""
    echo -e "${gl_huang}>>> 正在获取仓库所有标签 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
    
    # 初始获取标签列表(使用 Hub API 获取完整列表)
    TAGS=$(curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags?page_size=100" | jq -r '.results[].name')
    
    if [[ -z "${TAGS}" ]]; then
        log_error "获取标签失败或仓库无标签"
        exit 1
    fi

    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    log_info "当前仓库所有标签列表:"
    echo "${TAGS}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    local SELECT_TAG
    while true; do
        read -r -e -p "$(echo -e "${gl_bai}请输入要删除的标签名(${gl_hong}0${gl_bai}退出): ")" SELECT_TAG
        [[ "${SELECT_TAG}" = "0" ]] && exit_script
        if echo "${TAGS}" | grep -qw "${SELECT_TAG}"; then
            break
        else
            log_warn "输入的标签不存在,请重新输入"
        fi
    done

    echo -e ""
    echo -e "${gl_huang}>>> 标签 ${gl_lv}${SELECT_TAG}${gl_huang} 镜像摘要${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    log_info "正在获取标签 ${gl_huang}${SELECT_TAG}${gl_bai} 对应的镜像摘要 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
    
    # 获取 digest(从 Registry API 获取)
    DIGEST=$(curl -s -I -X GET \
        -H "Authorization: Bearer ${TOKEN}" \
        -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
        "https://registry-1.docker.io/v2/${REPO}/manifests/${SELECT_TAG}" 2>/dev/null | \
        grep -i "Docker-Content-Digest" | awk '{print $2}' | tr -d '\r')
    
    if [[ -z "${DIGEST}" ]]; then
        log_error "获取镜像摘要失败"
        exit 1
    fi
    
    log_info "镜像摘要: ${DIGEST}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    local CONFIRM
    read -r -e -p "$(echo -e "${gl_bai}确认删除标签 [${gl_huang}${SELECT_TAG}${gl_bai}] ?(${gl_lv}y${gl_bai}/${gl_hong}N${gl_bai}): ")" CONFIRM
    case "${CONFIRM}" in
        [Yy])
            echo -e ""
            echo -e "${gl_huang}>>> 开始执行删除操作 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
            echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
            
            RESPONSE=$(curl -s -X DELETE \
                -H "Authorization: Bearer ${TOKEN}" \
                -w "\n%{http_code}" \
                "https://registry-1.docker.io/v2/${REPO}/manifests/${DIGEST}")
            
            HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
            BODY=$(echo "$RESPONSE" | sed '$d')
            
            if [[ "$HTTP_CODE" == "202" ]] || [[ "$HTTP_CODE" == "204" ]]; then
                log_ok "标签删除成功!"
                echo -e "${gl_lv}✓ 标签 ${SELECT_TAG} 已被删除${gl_bai}"
            else
                log_error "删除失败 (HTTP ${HTTP_CODE})"
                echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
                exit 1
            fi

            echo -e ""
            echo -e "${gl_huang}>>> 正在获取仓库最新标签列表 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
            echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
            log_info "等待 3 秒让 Docker Hub 同步 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
            sleep 3
            
            log_info "使用 Hub API 获取最新标签列表 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
            NEW_TAGS=$(curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags?page_size=100" | jq -r '.results[].name')
            
            # 如果 Hub API 返回空,尝试 Registry API
            if [[ -z "${NEW_TAGS}" ]]; then
                log_info "Hub API 未返回标签,尝试 Registry API ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
                NEW_TAGS=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
                    "https://registry-1.docker.io/v2/${REPO}/tags/list" | jq -r '.tags[]?')
            fi

            echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
            log_info "当前仓库所有标签列表:"
            if [[ -n "${NEW_TAGS}" ]]; then
                echo "${NEW_TAGS}"
                if echo "${NEW_TAGS}" | grep -qw "${SELECT_TAG}"; then
                    log_warn "⚠ 标签 ${SELECT_TAG} 仍然存在,Docker Hub 可能需要更多时间同步"
                    log_info "请稍后手动检查或等待 1-2 分钟后再试"
                else
                    log_ok "✓ 标签 ${gl_huang}${SELECT_TAG}${gl_bai} 已成功从列表中移除"
                fi
            else
                echo -e "${gl_huang}⚠ 仓库暂无标签或 API 尚未同步${gl_bai}"
                log_warn "如果仓库确实还有标签,请稍后手动刷新查看"
            fi
            ;;
        [Nn])
            log_warn "已取消删除操作"
            ;;
        *)
            handle_y_n
            ;;
    esac

    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

main "$@"

实例

交互模式(推荐新手)

bash docker-hub_images_delete.sh

执行后依次输入:

  1. PAT 或 用户名:密码(输入 0 退出)
  2. 仓库名(直接回车使用默认 mobufan/cmdbox,输入 0 退出)
  3. 要删除的标签名(输入 0 退出)

传参模式(适合自动化)

# 传入 PAT 和仓库名,跳过多步输入
bash docker-hub_images_delete.sh dckr_pat_xxxxxx mobufan/cmdbox

查看仓库所有标签(不删除)

直接运行脚本,在输入标签名步骤输入一个不存在的标签名,脚本会提示重新输入,按 Ctrl+C 退出即可。

常见问题

Q: 删除标签后,Docker Hub 网页上还显示该标签?

A: Docker Hub 前端有缓存,通常等待 1~5 分钟即可刷新。也可以通过 Docker Hub API 确认标签是否已真正删除。

Q: 报错 "认证失败!请检查 PAT 或用户名密码是否正确"?

A: 确认 PAT 未过期且含有 delete 权限。如果使用密码认证,确认账号密码正确且未开启双因素认证(使用 PAT 可规避此问题)。

Q: 如何批量删除多个标签?

A: 当前脚本为交互式单次删除,批量删除可修改脚本在标签选择阶段支持多选,或多次运行脚本。

Q: 脚本支持删除多架构镜像的所有平台标签吗?

A: 支持。通过 Registry API 删除的是标签引用,与该标签指向的镜像层数多架构无关,删除后所有架构的该标签均不可用。

相关命令