Docker Image(镜像)使用指南

Docker Image(镜像)使用指南

八月 30, 2025 次阅读

Docker Image 介绍

一个 Docker 镜像是一个只读的模板,它包含了创建 Docker 容器所需的所有指令和文件系统内容。

我们可以通过以下方式来理解它:

  • 面向对象编程类比:镜像就像是,而容器就是这个类实例化出来的对象。一个类可以创建多个对象,同样,一个镜像可以运行出多个容器。
  • 操作系统安装盘类比:镜像就像是 .iso 光盘镜像文件,而容器就是使用这个光盘安装好并正在运行的操作系统。光盘是静态的、只读的,而安装好的系统是可运行的、可写的。

需要注意的是,不要将这些比喻中的“类”和“操作系统”的创建方式与 Docker 镜像和容器的创建方式混淆,他们的实现机制是完全不同的。

Docker Image 具有以下特性:

  • 只读:镜像一旦创建,其内容就不会改变。
  • 分层存储:镜像由一系列只读层组成,这是 Docker 的核心技术之一。
  • 内容寻址:现代 Docker 使用内容哈希(SHA256)来唯一标识镜像和其每一层,确保内容的一致性,为什么需要使用哈希,这就涉及到了 Dockerfile 的构建过程以及底层的存储机制,后续文章会详细讲解,这里主要是了解 Docker Image

镜像的组成:分层存储体系

这是理解镜像最关键、最精髓的部分。

什么是分层?

Docker 镜像并非一个巨大的单体文件,而是由一系列堆叠而成的。每一层代表了 Dockerfile 中的一条指令。

举个例子,一个简单的 Dockerfile:

FROM ubuntu:20.04         # 层 1: 拉取基础镜像层
COPY . /app               # 层 2: 添加当前目录文件到镜像的 /app 目录
RUN make /app             # 层 3: 使用 make 构建应用
CMD python /app/app.py    # 层 4: 设置容器启动时的默认命令

这个 Dockerfile 构建的镜像至少包含 4 层(实际上基础镜像 ubuntu:20.04 本身也是由多层组成的)。

分层的好处

  1. 共享和复用

    • 如果两个不同的镜像都基于 ubuntu:20.04,那么你的主机上只需要存储一份 ubuntu:20.04 的层。这极大地节省了磁盘空间和网络传输带宽。
    • 当你构建新镜像时,如果某一层(例如 RUN apt-get update)没有变化,Docker 会直接使用本地缓存的现有层,使得构建过程非常迅速。
  2. 减少磁盘占用

    • 容器在运行时,会在镜像的所有只读层之上,添加一个可写的容器层。所有对运行中容器的文件修改(如创建、删除、更改文件)都发生在这个薄薄的可写层中。这消除了对底层只读层的复制,使得容器启动极其轻量和快速。
  3. 高效分发

    • 当你 docker pushdocker pull 一个镜像时,Docker 引擎只会传输你本地没有的层。如果你更新了应用代码(只修改了 COPY 这一层),你只需要重新推送或拉取变化的那一层,而不是整个镜像。

镜像的构建:Dockerfile 与 docker build

镜像通常通过一个名为 Dockerfile 的文本文件来定义,并使用 docker build 命令构建在,这里只做一个简单的了解,大致看看即可,后续会详细讲解。

Dockerfile 主要指令:

  • FROM: 指定基础镜像,所有镜像的起点。
  • RUN: 在镜像层中执行命令(如安装软件包)。
  • COPY & ADD: 将文件从构建上下文复制到镜像中。
  • CMD & ENTRYPOINT: 指定容器启动时运行的命令。
  • EXPOSE: 声明容器运行时监听的端口。
  • ENV: 设置环境变量。
  • WORKDIR: 设置后续指令的工作目录。

构建过程:
当我们运行 docker build -t my-app:latest . 时:

  1. Docker 客户端将构建上下文(通常是 . 指定的当前目录)发送给 Docker 守护进程。
  2. 守护进程逐条执行 Dockerfile 中的指令。
  3. 每执行一条指令,就会创建一个新的镜像层(如果该指令修改了文件系统)。
  4. 所有指令执行完毕后,最终生成一个标记为 my-app:latest 的镜像。

镜像的存储与分发:Registry

镜像需要被存储和共享,这通过 Registry 实现。

  • Docker Hub: 默认的公共 Registry,就像 GitHub 对于代码一样。你可以从中拉取官方镜像(如 nginx, python)和社区镜像。
  • 其他公共 Registry: Harbor、Google Container Registry、Amazon ECR、Azure Container Registry 等。
  • 私有 Registry: 你可以搭建自己的私有 Registry(使用 Docker 官方提供的 registry 镜像或其他企业级方案如 Harbor),用于存储公司内部的私有镜像。

镜像名称结构: [registry-url]/[username]/[repository]:[tag]

  • registry-url: 默认为 docker.io(Docker Hub)。
  • username: 在 Docker Hub 或其他 Registry 上的用户名或组织名。
  • repository: 镜像仓库名,通常代表一个项目或应用。
  • tag: 标签,用于区分同一镜像的不同版本(如 latest, v1.0, dev)。最佳实践是避免使用浮动的 latest 标签,而使用明确的版本号。

常用命令:

  • docker pull nginx:alpine: 从 Registry 拉取镜像。
  • docker push my-company.com/dev/my-app:v1.2: 推送镜像到私有 Registry。
  • docker imagesdocker image ls: 列出本地镜像。
  • docker rmi <image_id>: 删除本地镜像。

深入底层:镜像的内容

一个镜像不仅仅是文件的集合,它还包含一些重要的元数据(JSON 格式):

  1. 镜像索引: 用于多架构镜像(如同时支持 amd64arm64),它指向不同平台对应的具体镜像清单。
  2. 镜像清单: 描述了一个特定平台镜像的配置文件和层信息。
  3. 镜像配置: 包含了该镜像的详细元数据,如:
    • 创建时间、作者
    • 容器运行时使用的配置(如环境变量、工作目录、入口点命令、暴露的端口)
    • 该镜像所包含的所有层的哈希值(rootfs.diff_ids
  4. 层文件: 每个层都是一个压缩包(tar.gz),包含了文件系统的变化。

我们可以通过 docker inspect <image_name> 命令来查看镜像的配置信息。


优化技巧

  1. 使用 .dockerignore 文件: 排除构建上下文中不必要的文件(如 node_modules, .git),加速构建过程并减小镜像体积。

  2. 选择小巧的基础镜像: 优先选择 Alpine Linux 等小型镜像,可以极大减小最终镜像体积和安全攻击面。

  3. 多阶段构建: 这是构建高效、小体积镜像的黄金法则。在一个 Dockerfile 中使用多个 FROM 指令。你可以在一个阶段(“构建阶段”)使用包含编译器等重型工具的镜像来构建应用,然后在另一个阶段(“运行阶段”)只复制构建好的二进制文件到一个非常精简的基础镜像中。

    # 阶段一:构建阶段
    FROM golang:1.19 AS builder
    WORKDIR /app
    COPY . .
    RUN go build -o myapp .
    
    # 阶段二:运行阶段
    FROM alpine:latest
    COPY --from=builder /app/myapp /usr/local/bin/
    CMD ["myapp"]
  4. 合并 RUN 指令: 将多个 RUN 指令用 && 连接成一个,减少镜像层数(虽然层数限制现在已不是大问题,但仍能减少一些元数据开销)。

  5. 明确的标签: 为生产环境镜像使用语义化版本号(如 v1.2.3)或 Git 提交哈希,而不是默认的 latest


Docker Image 常用指令

docker images

  • 功能
    列出本地存储的 Docker 镜像。

  • 语法

    docker images [OPTIONS] [REPOSITORY[:TAG]]
  • 别名

    docker image ls
    docker image list
  • 关键参数

    • -a, --all:显示所有镜像(包括中间镜像层,默认情况下会过滤掉中间镜像层)
    • --digests:显示镜像的摘要信息
    • -f, --filter:根据条件过滤显示结果
    • --format:使用 Go 模板格式化输出内容
    • --no-trunc:显示完整的镜像信息(不截断输出)
    • -q, --quiet:只显示镜像 ID
  • 使用示例

    # 列出本地所有镜像
    docker images
    
    # 列出特定的镜像(ubuntu 仓库的所有版本)
    docker images ubuntu
    
    # 显示所有镜像(包括中间层)
    docker images -a
    
    # 只显示镜像ID
    docker images -q
    
    # 显示完整的镜像信息(不截断)
    docker images --no-trunc
    
    # 使用过滤器显示特定标签的镜像
    docker images -f "reference=nginx:*"

docker tag

  • 功能
    为本地镜像创建新的标签,通常用于为镜像添加仓库地址前缀,为推送镜像到仓库做准备。

  • 语法

    docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
  • 别名

    docker image tag
  • 使用示例

    # 为本地镜像添加新的标签
    docker tag ubuntu:22.04 myregistry.com/myubuntu:22.04
    
    # 为镜像添加版本标签
    docker tag nginx nginx:1.23.3
    
    # 为镜像添加多个标签
    docker tag myapp:latest myregistry.com/myapp:v1.0
    docker tag myapp:latest myregistry.com/myapp:latest
    
    # 推送到私有仓库前的标签准备
    docker tag local-image:tag your-private-registry.com/username/repository:tag
  • 注意事项

    • docker tag 并不会创建新的镜像,只是为现有镜像添加了一个新的引用名称
    • 同一个镜像可以有多个标签,它们共享相同的镜像 ID
    • 删除一个标签不会删除镜像本身,只有当所有标签都被删除时,镜像才会被真正删除
    • 在推送镜像到仓库前,必须使用 docker tag 为镜像添加包含仓库地址的完整名称

docker pull

功能:从镜像仓库拉取(下载)镜像或仓库到本地。这是获取和运行容器的基础。

语法

docker pull [OPTIONS] NAME[:TAG|@DIGEST]
  • NAME:镜像名称,格式通常为 [仓库地址/]用户名/仓库名
  • TAG:镜像标签(版本)。如果省略,默认为 latest
  • DIGEST:镜像的数字摘要,用于精确指定某个版本的镜像,比 TAG 更唯一、安全。

示例

# 从 Docker Hub 拉取最新的 Ubuntu 镜像
docker pull ubuntu

# 拉取指定标签的 Nginx 镜像(版本为 1.25-alpine)
docker pull nginx:1.25-alpine

# 从私有仓库拉取镜像
docker pull myregistry.com:5000/myapp:v1.0

# 通过镜像摘要拉取,确保内容绝对一致
docker pull ubuntu@sha256:abc123...

注意事项

  • 总是明确指定标签(如 :alpine, :v1.0)而非依赖默认的 latest,以确保环境的一致性,通过官网中的 tag 复制对应仓库对应版本的镜像拉取命令是一个好习惯。

docker push

功能:将本地的镜像标签推送到镜像仓库。这是分享和部署自定义镜像的关键步骤。

语法

docker push [OPTIONS] NAME[:TAG]
  • 推送前,必须先用 docker tag 命令将本地镜像命名为符合目标仓库规范的名称(仓库地址/用户名/仓库名:标签)。

示例

# 1. 首先,为本地镜像打上符合仓库规范的标签
docker tag my_local_image:latest your_username/your_repo:v1.0

# 2. 然后,推送到仓库
docker push your_username/your_repo:v1.0

# 推送到私有仓库
docker push myregistry.com:5000/your_repo:v1.0

注意事项

  • 推送前必须确保已通过 docker login 登录且有相应权限。
  • 镜像推送是分层的操作的,如果仓库中已存在相同的层,则不会重复上传,节省时间和带宽。

docker rmi

功能:删除本地的一个或多个 Docker 镜像。释放磁盘空间或清理不再需要的镜像。

语法

docker rmi [OPTIONS] IMAGE [IMAGE...]
  • IMAGE:要删除的镜像名称或 ID。

示例

# 删除单个镜像
docker rmi ubuntu:22.04

# 删除多个镜像
docker rmi nginx:1.23.3 myapp:latest

注意事项

  • 只有在没有容器使用该镜像时,才能成功删除镜像。
  • 可以使用 -f 选项强制删除正在使用的镜像,但这可能会导致相关容器出现问题。

docker save、docker load

功能docker save 用于将一个或多个镜像保存为 tar 文件,方便迁移或备份;docker load 则用于从 tar 文件中加载镜像。

语法

docker save [OPTIONS] IMAGE [IMAGE...] > image.tar
docker load [OPTIONS] < image.tar
  • IMAGE:要保存或加载的镜像名称或 ID。
  • OPTIONS:可选参数,如 -o 指定输出文件。

示例

# 保存镜像为 tar 文件
docker save -o myimage.tar myapp:latest

# 从 tar 文件加载镜像
docker load -i myimage.tar

注意事项

  • docker save 保存的是镜像的所有层和元数据,生成的 tar 文件可以在其他 Docker 主机上使用 docker load 加载。
  • 使用 docker savedocker load 可以方便地在没有网络连接的环境中迁移镜像。

docker image inspect

  • 功能
    显示镜像的详细信息,包括元数据、配置信息、层信息等。

  • 语法

    docker image inspect [OPTIONS] IMAGE [IMAGE...]
  • 关键参数

    • -f, --format:使用给定的 Go 模板格式化输出
  • 使用示例

    # 查看指定镜像的详细信息
    docker image inspect nginx:1.23.3
    
    # 查看多个镜像的信息
    docker image inspect ubuntu:22.04 alpine:latest
    
    # 使用格式输出只查看镜像的架构信息
    docker image inspect -f '{{.Architecture}}' nginx:1.23.3
    
    # 查看镜像的创建时间
    docker image inspect -f '{{.Created}}' nginx:1.23.3
    
    # 查看镜像的所有层(Layer)
    docker image inspect -f '{{.RootFS.Layers}}' nginx:1.23.3

docker history

  • 功能
    查看镜像的历史记录,包括每一层的创建时间、作者、命令等信息。

  • 语法

    docker history [OPTIONS] IMAGE
  • 关键参数

    • -H, --human=false:以人类可读的格式显示大小
    • --no-trunc:显示完整的输出(不截断)
    • --quiet:只显示镜像 ID
  • 使用示例

    # 查看指定镜像的历史记录
    ┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/namespace_test/test_dir2] - [1634]
    └─[$] docker history nginx:stable-alpine3.21-perl                                                            [12:45:03]
    IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
    0b65b64c7d04   4 months ago   RUN /bin/sh -c set -x     && apkArch="$(cat …   38.4MB    buildkit.dockerfile.v0
    <missing>      4 months ago   RUN /bin/sh -c set -x     && apkArch="$(cat …   36.3MB    buildkit.dockerfile.v0
    <missing>      4 months ago   ENV NJS_RELEASE=1                               0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENV NJS_VERSION=0.8.10                          0B        buildkit.dockerfile.v0
    <missing>      4 months ago   CMD ["nginx" "-g" "daemon off;"]                0B        buildkit.dockerfile.v0
    <missing>      4 months ago   STOPSIGNAL SIGQUIT                              0B        buildkit.dockerfile.v0
    <missing>      4 months ago   EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENTRYPOINT ["/docker-entrypoint.sh"]            0B        buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 30-tune-worker-processes.sh /docker-ent…   4.62kB    buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 20-envsubst-on-templates.sh /docker-ent…   3.02kB    buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 15-local-resolvers.envsh /docker-entryp…   389B      buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 10-listen-on-ipv6-by-default.sh /docker…   2.12kB    buildkit.dockerfile.v0
    <missing>      4 months ago   COPY docker-entrypoint.sh / # buildkit          1.62kB    buildkit.dockerfile.v0
    <missing>      4 months ago   RUN /bin/sh -c set -x     && addgroup -g 1014.05MB    buildkit.dockerfile.v0
    <missing>      4 months ago   ENV DYNPKG_RELEASE=1                            0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENV PKG_RELEASE=1                               0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENV NGINX_VERSION=1.28.0                        0B        buildkit.dockerfile.v0
    <missing>      4 months ago   LABEL maintainer=NGINX Docker Maintainers <d…   0B        buildkit.dockerfile.v0
    <missing>      4 months ago   CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ADD alpine-minirootfs-3.21.4-x86_64.tar.gz /…   7.82MB    buildkit.dockerfile.v0
    
    # 以人类可读的格式查看历史记录
    ┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/namespace_test/test_dir2] - [1633]
    └─[$] docker history -H nginx:stable-alpine3.21-perl
    IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
    0b65b64c7d04   4 months ago   RUN /bin/sh -c set -x     && apkArch="$(cat …   38.4MB    buildkit.dockerfile.v0
    <missing>      4 months ago   RUN /bin/sh -c set -x     && apkArch="$(cat …   36.3MB    buildkit.dockerfile.v0
    <missing>      4 months ago   ENV NJS_RELEASE=1                               0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENV NJS_VERSION=0.8.10                          0B        buildkit.dockerfile.v0
    <missing>      4 months ago   CMD ["nginx" "-g" "daemon off;"]                0B        buildkit.dockerfile.v0
    <missing>      4 months ago   STOPSIGNAL SIGQUIT                              0B        buildkit.dockerfile.v0
    <missing>      4 months ago   EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENTRYPOINT ["/docker-entrypoint.sh"]            0B        buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 30-tune-worker-processes.sh /docker-ent…   4.62kB    buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 20-envsubst-on-templates.sh /docker-ent…   3.02kB    buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 15-local-resolvers.envsh /docker-entryp…   389B      buildkit.dockerfile.v0
    <missing>      4 months ago   COPY 10-listen-on-ipv6-by-default.sh /docker…   2.12kB    buildkit.dockerfile.v0
    <missing>      4 months ago   COPY docker-entrypoint.sh / # buildkit          1.62kB    buildkit.dockerfile.v0
    <missing>      4 months ago   RUN /bin/sh -c set -x     && addgroup -g 1014.05MB    buildkit.dockerfile.v0
    <missing>      4 months ago   ENV DYNPKG_RELEASE=1                            0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENV PKG_RELEASE=1                               0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ENV NGINX_VERSION=1.28.0                        0B        buildkit.dockerfile.v0
    <missing>      4 months ago   LABEL maintainer=NGINX Docker Maintainers <d…   0B        buildkit.dockerfile.v0
    <missing>      4 months ago   CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
    <missing>      4 months ago   ADD alpine-minirootfs-3.21.4-x86_64.tar.gz /…   7.82MB    buildkit.dockerfile.v0

可以看出来,docker history 是自带 -H 选项的,默认就是以人类可读的格式显示历史记录。

docker image prune

Docker 用久了,磁盘空间容易不足,docker image prune 💾 正是用来清理未使用的镜像,帮助释放磁盘空间的命令。我来为你详细讲解一下它的用法和注意事项。

理解“未使用的镜像”

Docker 对未使用的对象(如镜像、容器、卷和网络)采取保守的清理策略,除非明确要求,否则通常不会自动删除它们,这可能会导致 Docker 占用额外的磁盘空间。docker image prune 主要清理两类镜像:

  • 悬空镜像 (Dangling Images) :这是 docker image prune 默认清理的对象。它们是没有标签(Tag)且未被任何容器引用的镜像,通常是由于构建新镜像或重新打标签而产生的中间层或旧镜像。
  • 所有未使用的镜像 :使用 -a--all 参数时,此命令会删除所有未被任何容器引用的镜像,无论其是否有标签。这包括了那些你有名有姓但当前没有任何容器使用的镜像(例如,旧版本的 my-app:v1.0)。

命令语法和常用选项

docker image prune 的基本语法是:

docker image prune [OPTIONS]

常用的 [OPTIONS] 包括:

选项 作用 示例
-a, --all 删除所有未被容器引用的镜像,而不仅仅是悬空镜像。 docker image prune -a
-f, --force 强制删除,不显示确认提示。在脚本中常用。 docker image prune -f
--filter 使用过滤表达式来限制要修剪的镜像。 docker image prune -a --filter "until=24h"

常用操作示例

  1. 安全清理悬空镜像(推荐日常使用)
    这是最安全的做法,因为它只清理那些明确不再需要的“悬空”镜像。

    docker image prune

    执行后,Docker 会列出待删除的镜像并请求确认(除非使用 -f 强制跳过)。

  2. 清理所有未使用的镜像(彻底清理,需谨慎)
    这个命令会删除所有未被任何容器引用的镜像,包括有标签但未被使用的。

    docker image prune -a

    注意:此操作可能会删除一些你以后可能还会用到的镜像,下次使用前需要重新拉取或构建。

  3. 按条件过滤清理镜像
    你可以使用 --filter 来精确控制要删除的镜像。例如,删除所有创建时间超过 24 小时的未使用镜像:

    docker image prune -a --filter "until=24h"

    或者,删除所有创建时间超过 7 天的未使用镜像:

    docker image prune -a --filter "until=168h"
  4. 强制清理(用于脚本)
    如果你希望在脚本中自动执行清理,而不被交互提示打断,可以加上 -f--force 选项。

    docker image prune -f  # 强制删除悬空镜像
    docker image prune -a -f # 强制删除所有未使用的镜像

注意事项

  • 数据无价,操作前确认 :在执行 docker image prune -a 前,建议先确认哪些镜像会被删除。可以使用以下命令查看所有悬空镜像:

    docker images -f dangling=true

    若要查看所有镜像及其依赖关系,需仔细评估。

  • 被容器使用的镜像不会被删除无论容器是否正在运行,只要存在容器(包括已停止的容器)引用了该镜像,该镜像就不会被 prune 删除。这是 Docker 的数据保护机制。

清理操作的建议流程

一个比较安全的清理操作流程可以参考:

  1. 停止所有容器(如果希望清理更彻底):

    docker container stop $(docker container ls -aq)
  2. 执行系统级清理(根据需求选择是否加 --volumes):

    docker system prune -af --volumes
  3. 重启需要的服务(例如使用 Docker Compose):

    docker compose up -d

查看磁盘空间

在清理前后,你可以使用以下命令查看 Docker 的磁盘使用情况,了解清理效果:

docker system df

Docker Image 综合实践

离线迁移镜像

在日常开发、部署或环境迁移中,我们经常会遇到这样的场景:生产服务器无法访问外部网络( air-gapped environment)、需要快速部署一个特定版本的环境,或者只是想将开发好的应用镜像简单地“复制”到另一台机器上。这时,Docker 镜像的离线迁移就成了一项必备技能。

这里小编将通过一个完整的实验,带你一步步实践如何将一台主机上的 Docker 镜像打包、传输并加载到另一台主机上。

实验概述

实验目标:将主机 1(Source Host) 上的一个 busybox 镜像,完整地迁移到主机 2(Target Host) 上。

模拟环境

  • 主机 1:IP 为 [某内网IP],我们以 root 用户操作。
  • 主机 2:IP 为 82.156.255.140,我们以 ljx 用户操作。
  • (注:本次实验为演示方便,在同一台机器上模拟了两个主机的角色,但操作流程与真实双机环境完全一致)

使用工具

  • docker save / docker load
  • scp (Secure Copy)

第一步:在主机 1 上查找并打包镜像

首先,我们需要在源主机上找到想要迁移的镜像。

  1. 查找镜像
    使用 docker images 配合 grep 命令精确查找我们需要的 busybox 镜像及其唯一的 Image ID。

    docker images | grep busybox

    输出

    busybox  stable-glibc  1827167fde90  2 years ago  4.42MB

    我们记下这个关键的 Image ID:1827167fde90

  2. 打包镜像
    使用 docker save 命令将镜像打包成一个单一的 .tar 文件。这个文件包含了镜像的所有层(Layers)、元数据(Metadata)和标签(Tags)信息。

    docker save -o myimage.tar 1827167fde90
    • -o myimage.tar:指定输出文件名为 myimage.tar
    • 1827167fde90:指定要打包的镜像 ID。你也可以使用镜像名:标签(如 busybox:stable-glibc)。

    至此,一个完整的 Docker 镜像包 myimage.tar 就准备好了。

第二步:将镜像包传输到主机 2

我们使用 scp 命令,通过 SSH 协议安全地将文件从主机 1 拷贝到主机 2 的指定目录。这是跨主机操作的关键一步。

scp myimage.tar ljx@82.156.255.140:/home/ljx/docker_test/image_test/
  • myimage.tar:要传输的文件。
  • ljx@82.156.255.140:以 ljx 用户身份登录到 IP 为 82.156.255.140 的主机 2。
  • :/home/ljx/docker_test/image_test/:指定主机 2 上的目标路径。

如果是首次连接,会提示验证主机指纹,输入 yes 继续即可,然后输入用户 ljx 的密码。

第三步:在主机 2 上加载并验证镜像

现在,我们切换到主机 2 上进行操作。

  1. 验证文件已接收
    首先确认文件已成功传输并位于正确的目录下。

    ll ~/docker_test/image_test/

    输出

    total 4560
    -rw------- 1 ljx ljx 4668416 Aug 30 16:21 myimage.tar

    可以看到,一个约 4.5MB 的 myimage.tar 文件已经存在。

  2. 加载镜像
    使用 docker load 命令将打包文件还原为 Docker 镜像。

    docker load -i myimage.tar

    输出

    Loaded image ID: sha256:1827167fde90df99d9341a27fbce2b445550eb2b18105e03f98102f00c0ec35e

    恭喜!输出中的 Loaded image ID 表明镜像已成功加载到主机 2 的 Docker 镜像库中。

  3. 验证镜像
    最后,让我们检查一下镜像是否真的可以正常使用。

    docker images | grep 1827167fde90
    # 或者直接运行一个新的容器来测试
    docker run -it --rm 1827167fde90 echo "Hello from the migrated image!"

    如果能看到镜像列表和成功的输出,就证明整个离线迁移实验大功告成!


通过 docker savedocker load 进行镜像离线迁移,是一种非常可靠、通用的方法。它的优点是:

  • 环境无关:不依赖任何镜像仓库(如 Docker Hub、Harbor)。
  • 简单直接:命令易于理解和记忆。
  • 完整性:完美保留镜像的所有层和历史。

注意事项

  • 权限问题:在目标主机上执行 docker load 时,确保当前用户有操作 Docker 的权限(通常需要加入 docker 用户组)。
  • 存储空间:确保传输前后两台主机都有足够的磁盘空间。
  • 网络安全:使用 scp 传输时,数据是加密的,保证了镜像的安全性。
  • 标签丢失:有时 docker save 通过 Image ID 打包可能会丢失原有的镜像名和标签(Tag),加载后镜像可能变为 <none>:<none>。此时可以使用 docker tag <image_id> <new_name>:<new_tag> 为其重新打 tag。更推荐使用 docker save -o myimage.tar busybox:stable-glibc 这种镜像名:标签的方式来保存,可以保留所有信息。

镜像存储的压缩与共享

在容器镜像的分发过程中,存储压缩与共享机制对于提升效率、节省带宽至关重要。本次实验通过操作同一镜像的不同版本,直观展示了这一机制的工作原理。

从远程仓库拉取标签为 v1.0 的镜像到本地

docker pull crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v1.0

拉取过程显示完整的下载流程,包括分层信息的获取:

v1.0: Pulling from liujiaxuan/busybox_by_liujiaxuan
Digest: sha256:800a83edaed8daab01f81f408912d121d175066900dd422bdcb6c8c91dbb3268
Status: Downloaded newer image for crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v1.0

查看本地镜像信息,注意到解压后的镜像大小为 4.42MB

docker images | grep busybox

输出结果显示本地存储的实际大小:

crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan   v1.0   1827167fde90   2 years ago    4.42MB
busybox                                                                                       stable-glibc   1827167fde90   2 years ago    4.42MB

为演示共享机制,删除本地镜像后重新拉取

# 细节:删除是先去标签再删除
┌─[root@VM-16-15-debian] - [~] - [1687]
└─[$] docker rmi 1827167fde90 -f
Untagged: crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v1.0
Untagged: crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan@sha256:800a83edaed8daab01f81f408912d121d175066900dd422bdcb6c8c91dbb3268
Untagged: busybox:stable-glibc
Untagged: busybox@sha256:fea9e0f09e8cbbe7b2d2ca2ebb6e8da1e2e1d7c6ca7a4cf297eb2fcf5afda5ed
Deleted: sha256:1827167fde90df99d9341a27fbce2b445550eb2b18105e03f98102f00c0ec35e
Deleted: sha256:b4cb8796a924c1fe5cf7031b67a551c63f9236c5cb0e0d51af962285ae361db7
┌─[root@VM-16-15-debian] - [~] - [1688]
└─[$] docker pull crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v1.0
v1.0: Pulling from liujiaxuan/busybox_by_liujiaxuan
2dc65973bc71: Pull complete
Digest: sha256:800a83edaed8daab01f81f408912d121d175066900dd422bdcb6c8c91dbb3268
Status: Downloaded newer image for crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v1.0
crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v1.0

重新拉取时观察到完整的分层下载过程。

创建新版本标签并推送到仓库

docker tag crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v1.0 crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v2.0
docker push crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan:v2.0

推送过程显示分层已存在,无需重复上传:

The push refers to repository [crpi-x6zeb1ynyh83ir4y.cn-hangzhou.personal.cr.aliyuncs.com/liujiaxuan/busybox_by_liujiaxuan]
b4cb8796a924: Layer already exists
v2.0: digest: sha256:800a83edaed8daab01f81f408912d121d175066900dd422bdcb6c8c91dbb3268 size: 527

通过仓库管理界面查看,可以发现实际存储的镜像数据大小仅为 2.172MB,显著小于本地解压后的 4.42MB:
image

这一差异体现了镜像存储的压缩机制:镜像在传输和存储时采用压缩格式(2.172MB),而在本地运行时需要解压为可用的文件系统格式(4.42MB)。同时,基于相同镜像层(Digest 相同)的多个标签可以共享存储空间,推送新版本时只需上传元数据信息,大幅节省了网络带宽和存储成本。

这种设计将压缩解压的计算代价分散到各个客户端,虽然单个用户需要承担少量的计算开销,但整体上显著减轻了网络基础设施的压力,实现了”积少成多”的优化效果。