Docker pull镜像save后仅导出几KB:镜像 Layer 缺失问题排查与解决

Docker 29.2 + containerd 环境下 docker save 仅导出 17KB:镜像 Layer 缺失问题排查与解决

一、问题背景

在需要将 Docker 镜像从联网环境迁移到离线环境时,一般会使用:

docker pull <image>

docker save -o image.tar <image>

然后将 image.tar 拷贝到离线环境:

docker load -i image.tar

本次在迁移:

vllm/vllm-openai:latest

镜像时,却遇到了一个非常异常的问题:

  • 本地镜像大小约 30GB;
  • docker save 导出的 tar 文件只有约 17KB;
  • tar 文件可以正常解包,并且是 OCI Image Layout;
  • 归档中只有少量 manifest / metadata;
  • 真正的 filesystem layer 并没有被导出;
  • 将 tar 文件带到离线环境后无法正常使用;
  • 指定 --platform=linux/amd64 导出时,Docker 又提示该镜像不提供 amd64 平台。

最终确认,该现象与 Docker 29.2 + containerd image store 下的一个 Moby 官方 Bug 高度一致。

最终通过一次“空 Build”成功解决,而且不需要重新下载约 30GB 的 vLLM 镜像。


二、系统环境

本次出现问题的实际环境如下。

Docker Client

Version:           29.2.0
API version:       1.53
Go version:        go1.25.6
Git commit:        0b9d198
OS/Arch:           linux/amd64
Context:           default

Docker Server

Docker Desktop:    4.59.0
Engine Version:    29.2.0
API version:       1.53
OS/Arch:           linux/amd64
Experimental:      false

containerd

Version:           v2.2.1
GitCommit:         dea7da592f5d1d2b7755e3a161be07f43fad8f75

runc

Version:           1.3.4

目标镜像:

vllm/vllm-openai:latest

本地镜像大小约:

30GB

三、异常现象

正常情况下,一个约 30GB 的 Docker 镜像执行:

docker save \
  -o openaivllmlatest.tar \
  vllm/vllm-openai:latest

得到的 tar 至少应该是 GB 级。

但实际生成的文件只有:

约 17KB

这显然不可能是正常压缩。

进一步查看归档内容:

tar -tf openaivllmlatest.tar

得到:

blobs/
blobs/sha256/
blobs/sha256/07ea4e292adf3a26b05ac97114b28849cf4551a26beb1fbe7decd3842d752ed7
blobs/sha256/5b03c7de2951cda2d5fd087457902625eabbe9188478e439561ef34f325b0fd5
index.json
manifest.json
oci-layout

这里已经能够发现明显异常。

一个约 30GB 的大型 CUDA/vLLM 镜像,正常情况下:

blobs/sha256/

下面应该存在大量:

config
manifest
filesystem layer

而这里实际只有两个 blob。

也就是说:

30GB 并不是被压缩成了 17KB,而是几十 GB 的镜像 Layer 根本没有进入 docker save 生成的 tar。


四、进一步分析 Manifest

继续读取其中一个 blob:

tar -xOf openaivllmlatest.tar \
  blobs/sha256/07ea4e292adf3a26b05ac97114b28849cf4551a26beb1fbe7decd3842d752ed7 \
  | jq .

得到:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "size": 7238,
      "digest": "sha256:7441a0579b7974fe00eec2c41a5710bcd0dc3dafc03edcdc5663158dd68286cf",
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "size": 7239,
      "digest": "sha256:5b03c7de2951cda2d5fd087457902625eabbe9188478e439561ef34f325b0fd5",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

Manifest List 中明确存在:

linux/arm64
linux/amd64

于是继续尝试:

docker image save \
  --platform=linux/amd64 \
  -o openaivllm-amd64.tar \
  vllm/vllm-openai:latest

Docker 却返回:

Error response from daemon:
no suitable export target found:
image with reference vllm/vllm-openai:latest was found
but does not provide the specified platform (linux/amd64)

这时出现了一个非常有意思的矛盾:

Manifest List
     │
     ├── linux/arm64
     │
     └── linux/amd64
              │
              ▼
docker save --platform=linux/amd64
              │
              ▼
Docker:没有 linux/amd64

因此问题并不是:

vllm/vllm-openai 不支持 amd64

而是:

Docker 本地 image store 中虽然存在对应的 Manifest 信息,但对应平台的 Layer/Blob 没有处于 docker save 能够正确导出的状态。


五、官方 Issue

这个问题与 Moby 官方 Issue #52193 高度一致。

Issue 标题:

c8d storage issue with `docker save` after `docker pull`

官方地址:

https://github.com/moby/moby/issues/52193

该 Issue 创建于:

2026-03-19

并带有:

containerd-integration
kind/bug
version/29.2

等标签。

官方 Issue 的复现也是先执行:

docker pull --platform ...

然后执行:

docker save --platform ...

最终得到:

no suitable export target found:
image ... was found but does not provide the specified platform

Issue 中出现问题的环境包括:

Docker Engine 29.2.1
containerd 2.2.1
Docker Desktop

而我的环境是:

Docker Engine 29.2.0
containerd 2.2.1
Docker Desktop 4.59.0

两者高度接近。

更加关键的是,该 Issue 还明确说明:

Blobs that are expected to be present are missing

也就是说,本应存在的 Blob 实际无法被 exporter 获取,从而可能进一步产生:

missing layers
no such file or directory

等异常。


六、真正有效的解决方案

Moby #52193 中同时给出了一个非常有效的 workaround:

对准备进行 docker save 的镜像执行一次 Build。

本次实际测试同样有效。

执行:

docker build \
  --platform=linux/amd64 \
  -t vllm/vllm-openai:offline \
  - <<'EOF'
FROM vllm/vllm-openai:latest
EOF

这里实际上相当于创建了一个只有一行内容的 Dockerfile:

FROM vllm/vllm-openai:latest

没有:

RUN
COPY
ADD

也没有修改原始 vLLM 镜像里的任何内容。

它只是重新基于原镜像生成一个本地 Build 结果:

vllm/vllm-openai:latest
          │
          │ FROM
          ▼
vllm/vllm-openai:offline

完成以后重新导出:

docker image save \
  --platform=linux/amd64 \
  -o openaivllm-amd64.tar \
  vllm/vllm-openai:offline

这一次:

docker save

恢复正常。

生成的 tar 文件重新变成 GB 级,并且包含完整镜像 Layer。

之后即可复制到离线环境:

docker load -i openaivllm-amd64.tar

镜像可以正常使用。


七、为什么一次“空 Build”能够解决问题?

这部分需要注意,目前更适合作为根据官方 Issue 和实际测试结果进行的机制分析,而不是把它描述成 Docker 官方已经完全确认的底层根因。

问题发生时,可以简单理解成:

docker pull
     │
     ▼
containerd image store
     │
     ├── OCI Index 存在
     ├── Manifest 存在
     └── Blob / Layer 导出状态异常
                │
                ▼
           docker save
                │
                ▼
       只能导出少量 Metadata
                │
                ▼
              17KB

而执行:

FROM vllm/vllm-openai:latest

进行一次 Build 后,Docker/BuildKit 需要重新解析:

镜像 Manifest
平台
Config
基础镜像 Layer

并生成新的本地构建结果。

可以将这个过程理解成:

原镜像
   │
   │ docker pull
   ▼
containerd image store
   │
   └── Layer/Blob 对 save 不可正常导出
               │
               ▼
          docker build
               │
               ▼
      BuildKit 重新解析基础镜像
               │
               ▼
      生成新的本地 Image Result
               │
               ▼
        Layer/Blob 可以被 exporter 获取
               │
               ▼
          docker save 正常

因此,与其说:

镜像损坏了

不如说:

Docker 29.2 + containerd image store 场景下,docker pull 后的镜像在 docker save 导出过程中出现了 Blob/Layer 的可见性或导出状态异常;重新执行 Build 可以触发 Docker/BuildKit 重新处理这些 Layer,从而使其恢复为可正常导出的状态。


八、如何确认自己遇到的是同一个问题?

如果出现以下组合现象,就非常值得怀疑是相同问题。

首先:

docker images

镜像显示:

10GB
20GB
30GB

甚至更大。

但是:

docker save -o image.tar image:tag

得到的文件却只有:

几 KB
几十 KB

查看:

tar -tf image.tar

发现只有:

blobs/
blobs/sha256/
index.json
manifest.json
oci-layout

并且:

blobs/sha256/

下面只有极少量文件。

同时再执行:

docker save \
  --platform=linux/amd64 \
  -o image.tar \
  image:tag

出现:

no suitable export target found

或者:

image ... was found but does not provide the specified platform

而 Manifest List 中明明存在:

linux/amd64

那么这个问题与 Moby #52193 就非常接近了。


九、导出以后一定要进行验证

问题解决以后,不建议看到 docker save 没有报错就直接把文件带进离线环境。

至少做下面几个检查。

1. 检查文件大小

du -h openaivllm-amd64.tar

如果原镜像是几十 GB:

17KB

显然不正常。

恢复以后应该是:

GB 级

2. 检查 Blob 数量

tar -tf openaivllm-amd64.tar \
  | grep '^blobs/sha256/' \
  | wc -l

原来的异常镜像只有极少量 Blob。

正常的大型 vLLM 镜像应该存在大量:

blobs/sha256/xxxxxxxx

3. 查看归档内容

tar -tf openaivllm-amd64.tar | head -50

应该能够看到大量:

blobs/sha256/xxxx
blobs/sha256/xxxx
blobs/sha256/xxxx
...

4. 计算 SHA256

在线机器:

sha256sum openaivllm-amd64.tar

复制到离线环境以后:

sha256sum openaivllm-amd64.tar

两边结果应该完全一致。

这样可以排除移动硬盘、U盘、网络传输等过程导致的大文件损坏。


十、备用方案:关闭 containerd image store

Moby #52193 中还提到:

关闭 Docker Desktop 中的:

Use containerd for pulling and storing images

以后,docker save 可以恢复正常。

Docker Desktop 可以进入:

Settings
→ General
→ Use containerd for pulling and storing images

取消该选项。

Docker 官方关于 containerd image store 的说明:

https://docs.docker.com/desktop/features/containerd/

需要注意的是:

classic image store 与 containerd image store 是两套独立的镜像存储。

因此切换以后,之前的镜像可能暂时不会出现在:

docker images

中。

这并不代表镜像被删除。

重新切回原来的 image store 后又可以看到。

如果镜像有:

20GB
30GB
40GB

那么切换 image store 后重新 docker pull 的成本比较高。

所以对于这种大型 AI/CUDA 镜像,我更推荐优先尝试:

空 Build

成功以后直接:

docker save

十一、推荐排查流程

以后遇到类似问题,可以按照:

docker save 文件异常小
          │
          ▼
tar -tf 检查内容
          │
          ▼
是否只有 Manifest / Metadata?
          │
         Yes
          │
          ▼
检查 docker version
检查 containerd version
          │
          ▼
docker save --platform
          │
          ▼
是否提示 platform 不存在?
          │
         Yes
          │
          ▼
执行一次空 Build
          │
          ▼
docker save 新镜像
          │
      ┌───┴────┐
      │        │
    成功      失败
      │        │
      ▼        ▼
   完成     关闭 containerd
             image store
                │
                ▼
             重新 pull
                │
                ▼
             docker save

十二、最终可直接使用的命令

如果以后再次遇到类似情况,可以直接尝试:

1. 重新生成本地镜像

docker build \
  --platform=linux/amd64 \
  -t vllm/vllm-openai:offline \
  - <<'EOF'
FROM vllm/vllm-openai:latest
EOF

2. 导出 amd64 镜像

docker image save \
  --platform=linux/amd64 \
  -o openaivllm-amd64.tar \
  vllm/vllm-openai:offline

3. 检查文件大小

du -h openaivllm-amd64.tar

4. 检查 Blob

tar -tf openaivllm-amd64.tar \
  | grep '^blobs/sha256/' \
  | wc -l

5. 生成 SHA256

sha256sum openaivllm-amd64.tar

6. 离线机器加载

docker load -i openaivllm-amd64.tar

十三、总结

这次问题最具有迷惑性的地方在于:

docker images

明明显示:

约 30GB

但:

docker save

却只生成:

约 17KB

进一步检查 OCI archive 后发现,17KB 文件基本只有:

OCI Index
Manifest
Metadata

真正几十 GB 的 filesystem layer 并没有进入 tar。

同时:

docker save --platform=linux/amd64

还出现:

image ... was found but does not provide the specified platform

但是 Manifest List 明明声明存在:

linux/amd64

结合:

Docker Engine 29.2.0
Docker Desktop 4.59.0
containerd 2.2.1

以及 Moby 官方 Issue #52193,可以判断该问题与 Docker 29.2 下 containerd image store 的 docker save Bug 高度一致。

最终解决方法非常简单:

docker build \
  --platform=linux/amd64 \
  -t vllm/vllm-openai:offline \
  - <<'EOF'
FROM vllm/vllm-openai:latest
EOF

然后重新:

docker image save \
  --platform=linux/amd64 \
  -o openaivllm-amd64.tar \
  vllm/vllm-openai:offline

即可恢复正常。

对于几十 GB 的 vLLM、PyTorch、CUDA 等大型 AI 镜像,这种方案最大的优点是:

通常不需要重新下载整个镜像,就能解决 docker save 缺少 Layer 的问题。


十四、参考资料

Moby 官方 Issue

#52193 - c8d storage issue with docker save after docker pull

https://github.com/moby/moby/issues/52193

Moby 相关历史 Issue

#47065 - [v25] docker save produces an OCI manifest with no layers

https://github.com/moby/moby/issues/47065

Docker 官方文档:containerd image store

https://docs.docker.com/desktop/features/containerd/

Docker 官方文档:docker image save

https://docs.docker.com/reference/cli/docker/image/save/

Docker 官方文档:docker image load

https://docs.docker.com/reference/cli/docker/image/load/

posted @ 2026-08-11 11:03  日报初级开发工程师  阅读(21)  评论(0)    收藏  举报