Docker 镜像拉取与离线分发实践

Docker 镜像拉取与离线分发实践

背景

在内网部署、离线交付、跨环境迁移时,常见需求不是直接运行容器,而是先把镜像拉到本地,再导出成文件发给其他人。

这类场景下,最稳妥的流程通常是:

  1. docker pull 将目标镜像拉到本地
  2. docker save 将镜像导出为 .tar
  3. 按统一命名规则整理文件
  4. 将导出的包分发给目标环境
  5. 对方使用 docker load 导入

本文整理一套可直接复用的操作方式。

核心命令

拉取镜像:

bash 复制代码
docker pull 镜像名:tag

导出镜像:

bash 复制代码
docker save -o myimage.tar 镜像名:tag

例如:

bash 复制代码
docker pull nginx:latest
docker save -o nginx--latest.tar nginx:latest

为什么用 docker save

docker save 导出的是镜像本身,适合做离线分发。

它和下面几种方式的区别需要分清:

  • docker save
    导出镜像,适合发送给别人导入。
  • docker export
    导出容器文件系统,不保留镜像层和元数据,通常不适合镜像分发。
  • 直接拷贝 Docker Desktop 的底层磁盘文件
    不适合单镜像交付,文件大且不可控。

如果目标是"把某个镜像发给别人用",优先选 docker save

命名规则

为了便于识别和批量管理,可以将导出文件命名为:

text 复制代码
镜像名最后一个 path--tag.tar

例如:

text 复制代码
docker.io/library/nginx:latest

导出后命名为:

text 复制代码
nginx--latest.tar

再例如:

text 复制代码
docker.io/library/mysql:8.0

导出后命名为:

text 复制代码
mysql--8.0.tar

这个规则的优点很直接:

  • 文件名短,适合分发
  • 能保留服务名和版本号
  • 比把完整仓库路径塞进文件名更清晰

单个镜像导出

下面是一套最小操作:

bash 复制代码
image="nginx:latest"
repo="${image%:*}"
tag="${image##*:}"
name="${repo##*/}"

docker pull "$image"
docker save -o "${name}--${tag}.tar" "$image"

执行后会生成:

text 复制代码
nginx--latest.tar

批量导出镜像

如果镜像很多,手工执行容易出错。更实用的做法是写一个简单脚本。

bash 复制代码
#!/usr/bin/env bash
set -u

out_dir="./web-images"
mkdir -p "$out_dir"

images=(
  "nginx:latest"
  "mysql:8.0"
  "redis:7.2"
)

for image in "${images[@]}"; do
  repo="${image%:*}"
  tag="${image##*:}"
  name="${repo##*/}"
  tar_path="${out_dir}/${name}--${tag}.tar"

  echo "=== $image ==="
  docker pull "$image" || continue
  docker save -o "$tar_path" "$image" || continue
  echo "SAVED -> $tar_path"
done

这个脚本的特点是:

  • 逻辑简单,便于维护
  • 目录按用途分开,比如 web-imagesdb-images
  • 文件名统一
  • 某个镜像失败不会阻塞后续镜像

实际整理方式

如果项目里存在多套业务镜像,建议按目录区分:

text 复制代码
web-images/
  nginx--latest.tar
  redis--7.2.tar

db-images/
  mysql--8.0.tar
  postgres--16.tar

这样比全部堆在一个目录里更好管理,尤其适合:

  • 按系统交付
  • 按环境备份
  • 按项目归档

常见问题

1. docker save 生成的是不是"压缩包"

严格来说,docker save 生成的是 .tar 包,不一定是压缩过的。

如果你还想进一步压缩,可以再执行:

bash 复制代码
gzip nginx--latest.tar

压缩后会变成:

text 复制代码
nginx--latest.tar.gz

但很多场景下,直接保留 .tar 就已经够用了。

2. 为什么 docker pull 会提示 unauthorized

这通常不是命令写错,而是仓库权限问题。常见原因有:

  • 没有执行 docker login
  • 当前账号没有目标仓库拉取权限
  • 镜像路径写错
  • tag 不存在

遇到这种情况,先确认:

bash 复制代码
docker login

然后再重试 docker pull

3. docker save 会不会带上容器运行时的数据

不会。

docker save 只导出镜像,不包含容器运行过程中写入的数据卷内容。

如果你需要一起迁移业务数据,还要额外处理:

  • Docker volume
  • 挂载目录
  • 数据库备份

4. 对方如何使用导出的镜像

对方收到 .tar 后,直接执行:

bash 复制代码
docker load -i nginx--latest.tar

导入完成后,可通过下面命令确认:

bash 复制代码
docker images

推荐的交付流程

比较稳妥的一套流程如下:

  1. 整理镜像清单
  2. 创建分类目录,例如 web-imagesdb-images
  3. 批量执行 docker pull
  4. 服务名--版本.tar 导出
  5. 校验导出文件是否齐全
  6. 如有需要,再统一压缩或打总包
  7. 交付对方使用 docker load 导入

一套可直接复用的模板

bash 复制代码
#!/usr/bin/env bash
set -u

out_dir="./images"
mkdir -p "$out_dir"

images=(
  "镜像1:tag"
  "镜像2:tag"
  "镜像3:tag"
)

for image in "${images[@]}"; do
  repo="${image%:*}"
  tag="${image##*:}"
  name="${repo##*/}"
  tar_path="${out_dir}/${name}--${tag}.tar"

  echo "pull -> $image"
  docker pull "$image" || {
    echo "pull failed: $image"
    continue
  }

  echo "save -> $tar_path"
  docker save -o "$tar_path" "$image" || {
    echo "save failed: $image"
    continue
  }
done

总结

如果目标是做离线交付,最实用的办法就是:

text 复制代码
docker pull -> docker save -> 统一命名 -> 分类归档 -> docker load

这里最关键的不是命令本身,而是两点:

  • 命名规则要统一
  • 目录结构要清晰

当镜像数量一多,规范化管理比单次执行更重要。