概述



在构建 Docker 镜像时,容量意外增大是常见现象。大多数原因在于 不必要的层 被创建。本文将使用 docker history 命令来查看镜像的“家谱”,并分享通过此方法轻量化实际镜像的优化经验。

通过这个命令,可以准确找到使镜像变重的原因,并创建更高效的镜像。


docker history: 查看镜像的家谱

docker history 是一个命令,用于显示特定镜像的创建过程和构成细节。它可以显示 Dockerfile 中每个命令是如何叠加成 层(layer) 的,以及每层的生成时间和大小。

基本用法非常简单。

docker history [选项] <镜像名称:标签>

例如,查看本地 my-app:latest 镜像历史的命令如下所示。

docker history my-app:latest

输出结果将按顺序显示从镜像最新的最顶层到基础镜像(最旧的)。

IMAGE          CREATED         CREATED BY                                      SIZE
a6215f271958   5 minutes ago   /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      7 weeks ago     /bin/sh -c #(nop) ADD file:f28242cf608f6...   7.81MB
  • CREATED BY: 生成此层的 Dockerfile 命令

  • SIZE: 此层占用的大小

实用提示: 使用 --no-trunc 查看完整命令

docker history 的默认输出如果 CREATED BY 列的命令过长,会被截断。在这种情况下,使用--no-trunc 选项可以查看完整命令,进行精确分析是至关重要的。

docker history --no-trunc my-app:latest

层分析的重要性: 实际优化经验



Docker 镜像的基本原则是为 Dockerfile 中的 每一行命令生成一个层。每层是对前一层的“更改”。

问题在于,如果不必要地分层,镜像的大小会急剧增加。

案例: COPYRUN chown 的陷阱

在编写 Dockerfile 时,通常将项目文件复制到容器中(COPY),然后更改这些文件的所有权(RUN chown)。

低效方法: 产生两个层

最初,Dockerfile 被编写如下。

# 1. 复制文件 (复制为 root 所有)
COPY . .

# 2. 更改所有权 (单独命令执行)
RUN chown -R appuser:appgroup /app

通过 docker history 查看生成的镜像历史,可以看到产生了两个层。

IMAGE          CREATED BY                                        SIZE
<layer_id_2>   /bin/sh -c chown -R appuser:appgroup /app        150MB  <-- (问题!)
<layer_id_1>   /bin/sh -c #(nop) COPY dir:abc in /app           150MB

这里出现了一个严重的问题。

  1. COPY . . 命令将150MB大小的文件添加到 层 1

  2. RUN chown 命令将 层 1 的150MB文件复制 并仅更改所有权信息,然后再存入 层 2

文件内容相同,但一个为 root 所有,一个为 appuser 所有,分别存储在不同的层中。结果是这两个层的 镜像总容量达300MB(150MB + 150MB),

高效方法: 1个层

这个问题可以通过使用 COPY 命令的 --chown 选项来简单解决。

# 1. 复制并同时指定所有权
COPY --chown=appuser:appgroup . .

修改 Dockerfile 后,再次通过 docker history 查看。

IMAGE          CREATED BY                                             SIZE
<layer_id_1>   /bin/sh -c #(nop) COPY --chown=appuser... dir:abc     150MB

仅生成一个层,镜像的总大小减少到 150MB。仅仅将两行命令合并为一行,镜像大小几乎减半。


结论

docker history 不只是查看镜像生成历史,它是 优化镜像的关键分析工具

修改 Dockerfile 后,习惯性地执行 docker history --no-trunc 来检查层是否按预期生成,以及是否存在占用不必要空间的层,这是一个好习惯。这个小习惯将有助于构建轻量高效的镜像。