用 Python 实现 Docker 镜像批量推送(带进度条)

背景

在本地调试或搭建漏洞复现环境时,我们经常会使用到各种公开的 Docker 镜像(如 vulhub/、jboss/、portainer/ 等)。但出于以下原因,你可能需要将这些镜像迁移到自己的 Docker Hub 仓库:

  • 防止官方仓库删除或无法访问
  • 在隔离环境中部署
  • 长期保存当前使用的镜像版本
  • 配合自己的 docker-compose.yml 统一管理

手动一个个 docker tag + docker push 很繁琐,尤其当镜像数量多、体积大时。因此我写了一个 Python 脚本,它可以:

  • ✅ 自动读取当前所有容器(包括停止的)使用的镜像
  • ✅ 去除原始仓库前缀(如 vulhub/),打上自己的仓库标签
  • ✅ 带 实时进度条 (基于 tqdm)显示每一层的上传进度
  • ✅ 增量上传:已存在的层会自动跳过,不会重复消耗流量

脚本代码

将以下代码保存为 push_images_with_progress.py:

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import docker
import sys
from tqdm import tqdm

# ========== 配置 ==========
DOCKER_USERNAME = "rockmelodies"   # 你的 Docker Hub 用户名
# =========================

client = docker.from_env()

def get_unique_images_from_all_containers():
    """获取所有容器(包括停止的)使用的镜像名(唯一)"""
    containers = client.containers.list(all=True)
    images = set()
    for c in containers:
        # 镜像名格式:repository:tag
        image_full = c.image.tags[0] if c.image.tags else f"{c.image.id[:12]}"
        images.add(image_full)
    return images

def short_image_name(full_name):
    """从 vulhub/nginx:1.11.13 提取 nginx:1.11.13"""
    # 去掉第一个 / 之前的所有内容
    if '/' in full_name:
        return full_name.split('/', 1)[-1]
    return full_name

def tag_and_push(image_full):
    """打标签并推送,显示进度条"""
    short = short_image_name(image_full)
    new_tag = f"{DOCKER_USERNAME}/{short}"
    
    print(f"\n🚀 处理镜像: {image_full}")
    print(f"   → 新标签: {new_tag}")
    
    # 1. 拉取原始镜像(确保本地存在)
    try:
        client.images.get(image_full)
    except docker.errors.ImageNotFound:
        print(f"   ⚠️ 本地未找到 {image_full},尝试拉取...")
        client.images.pull(image_full)
    
    # 2. 打标签
    img = client.images.get(image_full)
    img.tag(new_tag)
    
    # 3. 推送(带进度条)
    print("   ⬆️ 开始推送...")
    layers_progress = {}
    
    for line in client.images.push(new_tag, stream=True, decode=True):
        if 'id' in line and 'progressDetail' in line:
            layer_id = line['id']
            progress = line['progressDetail']
            total = progress.get('total')
            current = progress.get('current')
            
            if total and current:
                if layer_id not in layers_progress:
                    layers_progress[layer_id] = tqdm(
                        total=total, unit='B', unit_scale=True,
                        desc=f"    层 {layer_id[:12]}", leave=False
                    )
                layers_progress[layer_id].update(current - layers_progress[layer_id].n)
                if current >= total:
                    layers_progress[layer_id].close()
                    del layers_progress[layer_id]
        elif 'status' in line:
            status = line['status']
            if 'already exists' in status.lower():
                tqdm.write(f"   ℹ️ {status}")
    
    print(f"   ✅ 推送完成: {new_tag}\n")

def main():
    print("🔍 获取所有容器使用的镜像...")
    images = get_unique_images_from_all_containers()
    if not images:
        print("❌ 没有找到任何容器镜像。")
        sys.exit(1)
    
    print(f"📋 找到 {len(images)} 个唯一镜像:")
    for img in images:
        print(f"   - {img}")
    
    for img in images:
        try:
            tag_and_push(img)
        except Exception as e:
            print(f"❌ 推送失败 {img}: {e}")
    
    print("\n🎉 所有镜像处理完毕!")

if __name__ == "__main__":
    main()

使用步骤

1. 安装依赖

bash 复制代码
pip install docker tqdm

如果提示 pip 未找到,请先确保 Python 环境正常(必要时使用 python -m ensurepip --upgrade)。

2. 登录 Docker Hub

bash 复制代码
docker login

输入你的 Docker ID 和 个人访问令牌(推荐) 或密码。

3. 修改脚本中的用户名

将 DOCKER_USERNAME = "rockmelodies" 改为你自己的 Docker Hub 用户名。

4. 运行脚本

bash 复制代码
python3 push_images_with_progress.py

运行效果示例

复制代码
🔍 获取所有容器使用的镜像...
📋 找到 11 个唯一镜像:
   - vulhub/comfyui:3.39.1-with-manager
   - vulhub/webmin:1.910
   ...

🚀 处理镜像: vulhub/comfyui:3.39.1-with-manager
   → 新标签: rockmelodies/comfyui:3.39.1-with-manager
   ⬆️ 开始推送...
    层 a1b2c3d4e5f6: 45%|████▌     | 45.2M/100M [00:05<00:06, 8.2MB/s]
   ℹ️ Layer already exists
   ✅ 推送完成: rockmelodies/comfyui:3.39.1-with-manager

🎉 所有镜像处理完毕!

核心优势

🔁 增量上传(不会重复推送)

  • 脚本使用 docker.images.push(stream=True) 获取推送过程的实时输出。
  • Docker 底层采用分层存储 ,如果远程仓库已有某层(如之前推送过),会显示 Layer already exists 并直接跳过,不会重新传输数据。
  • 因此即使多次运行脚本,也不会浪费流量和时间。

📊 精确进度条

  • 基于 tqdm,为每一个正在上传的镜像层绘制独立的进度条。
  • 实时显示已传输大小、总大小、传输速度。
  • 当层传输完成后进度条自动关闭。

🧠 自动提取镜像列表

  • 调用 Docker API 获取所有容器(all=True 包括已停止的)。
  • 自动去重,同一个镜像不会重复处理。
  • 无需手动维护镜像列表,适合动态环境。

🏷️ 智能打标签

  • 自动去除原始仓库前缀(如 vulhub/、jboss/、portainer/)。
  • 例如:vulhub/nginx:1.11.13 → 你的用户名/nginx:1.11.13
  • 简洁且符合 Docker Hub 命名规范。

常见问题

Q1:推送时提示 denied: requested access to the resource is denied

A :没有登录或登录凭据无效。执行 docker login 并确认用户名/令牌正确。

Q2:脚本运行时卡住不动?

A:大型镜像的上传需要时间,进度条可能会短暂没有更新,实际仍在传输。请耐心等待。如果长时间无响应,检查网络连接。

Q3:我不想推送所有镜像,只想推送某几个怎么办?

A :可以修改 get_unique_images_from_all_containers() 函数,增加过滤逻辑;或直接在脚本中定义一个白名单列表。

Q4:推送完成后如何确认镜像在 Docker Hub 上?

A :登录 https://hub.docker.com/u/你的用户名 查看仓库列表。也可以执行 docker pull 你的用户名/镜像名:标签 测试。


扩展想法

  • 可以添加 --dry-run 模式,只显示将要推送的镜像而不实际执行。
  • 支持多个目标仓库(如同时推送到阿里云镜像仓库)。
  • 增加失败重试机制和日志记录。

总结

这个 Python 脚本让你可以用一条命令将本地所有运行过的 Docker 镜像完整迁移到自己的 Docker Hub 仓库,并实时看到推送进度。而且由于 Docker 分层的特性,重复执行也不会造成资源浪费。

如果你也在管理多个漏洞复现环境或自定义镜像,不妨试试这个脚本,让镜像迁移变得轻松又透明。


欢迎留言交流,如果你有更好的改进建议,也欢迎提出来!

相关推荐
全栈练习生8 分钟前
AI安全与红队测试
python·ai
费曼学习法14 分钟前
把「发送成功」当「送达」?我排查 IM 长消息被静默吞掉的完整过程
python·ai编程·自动化运维
鸽鸽程序猿16 分钟前
【项目】【在线判题系统】b端题目增删改
开发语言·python
程序员老赵16 分钟前
Docker 部署 Filestash:轻松搭建浏览器文件管理平台
前端·docker·开源
NetX行者24 分钟前
Python 工程化入门:从脚本到规范的 Python 项目结构
网络·python
艾伦_耶格宇34 分钟前
【DOCKER容器实战】-5 Docker容器镜像的构建
运维·docker·容器
MasonYyp36 分钟前
asyncio并发协程
python
小小张说故事1 小时前
Python 的整数为什么永远不会溢出?拆开大整数对象,看 30 位一节的存储与运算代价
python
用户298698530141 小时前
Python 实现 Word 与 TIFF 双向转换的方案
后端·python·api
benchmark_cc1 小时前
实时行情和分钟 K 线应该如何配合使用?从信号计算到交易决策的完整分析
大数据·python·数据分析·pandas·量化交易·股票数据·quantdash