如何将我首个发布的Docker镜像体积缩减40%——关于将Shell脚本Docker化的经验分享

工作中我每天都要接触Dockerfile,自己也写过几个,搭建过容器等等。但从未在Docker Hub注册表上发布过镜像。我想让ugit – 撤销Git命令的工具(以Shell脚本形式编写)能供那些不愿从网上安装随机Shell脚本的人使用。

我知道,我知道。用Go/Rust/魔法语言重写吧。但这段脚本已有500多行Bash代码,除非有人拿枪指着我(或者最终赞助我??),否则我绝不会改写。况且ugit功能已基本完善(只剩几个不常用的撤销命令未实现)。

总之,本文后续将详细说明我如何为ugit(一个shell脚本)编写官方Dockerfile,并通过逐步引导式优化尝试将镜像大小缩减近40% (从31.4 MB降至17.6 MB)的官方Dockerfile。希望这能激励其他Shell爱好者也将其脚本发布为Docker镜像!

PS:我并非DevOps或Docker专家,若您是相关领域的行家,发现任何错误或改进空间,请在评论区告知或通过此处联系我。最终Docker镜像已发布于Docker Hub

首个 Dockerfile 尝试

元素周期表
# Use an official Alpine runtime as a parent image
FROM alpine:3.18

# Set the working directory in the container to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install dependencies
RUN apk add --no-cache 
    bash 
    gawk 
    findutils 
    coreutils 
    git 
    ncurses 
    fzf

# Set permissions and move the script to path
RUN chmod +x ugit && mv ugit /usr/local/bin/

# Run ugit when the container launches
CMD ["ugit"]

看起来很简单吧?确实如此。只要当前目录已克隆 ugit,你完全可以直接复制粘贴这个 Dockerfile 自行构建镜像。

docker build -t ugit .
docker run --rm -it -v $(pwd):/app ugit

这应该能在容器内成功运行 ugit。

ugit 需要以下二进制文件:bash (>=4.0)awkxargsgitfzftputcuttrnl

  • 我们必须安装 findutils,因为它包含 xargs
  • 我们必须安装coreutils,因为它包含trcutnl
  • ncursestput(用于获取终端信息)的依赖项。

以上就是UNIX系统或容器中运行ugit所需的全部组件。当前镜像大小为31.4 MB,初次尝试已相当不错。接下来我们将探索进一步压缩的方法。

优化路径——缩减镜像大小40%

在后续章节的(极致)微优化尝试中,我们将围绕以下核心目标展开:

  • 采用多阶段构建缩减镜像体积
  • 移除sleepwatchdu等非ugit运行必需的二进制文件
  • 清除这些二进制工具引入的冗余依赖项
  • ugit运行所需的所有依赖项降至最低版本
  • 最终镜像仅加载必需的二进制工具及其依赖项

第二轮尝试 – alpine容器运行alpine镜像

我现选择创建多阶段构建。第二阶段将仅用于复制所需二进制文件及其依赖项。我再次选择使用alpine作为该阶段的基础镜像。

# First stage: Install packages
FROM alpine:3.18 as builder

RUN apk add --no-cache 
    bash 
    gawk 
    findutils 
    coreutils 
    git 
    ncurses 
    fzf

# Copy only the ugit script into the container at /app
WORKDIR /app
COPY ugit .

# Set permissions and move the script to path
RUN chmod +x ugit && mv ugit /usr/local/bin/

# Second stage: Copy only necessary binaries and their dependencies
FROM alpine

COPY --from=builder /usr/local/bin/ugit /usr/bin/
COPY --from=builder /usr/bin/git /usr/bin/
COPY --from=builder /usr/bin/fzf /usr/bin/
COPY --from=builder /usr/bin/tput /usr/bin/
COPY --from=builder /usr/bin/cut /usr/bin/
COPY --from=builder /usr/bin/tr /usr/bin/
COPY --from=builder /usr/bin/nl /usr/bin/
COPY --from=builder /usr/bin/gawk /usr/bin/
COPY --from=builder /usr/bin/xargs /usr/bin
COPY --from=builder /usr/bin/env /bin/
COPY --from=builder /bin/bash /bin/

WORKDIR /app
# Run ugit when the container launches
CMD ["ugit"]

通过简单的多阶段构建,我们成功将镜像大小压缩至惊人的20.6 MB。当前镜像虽能成功构建,但仍无法运行ugit。

Error loading shared library libreadline.so.8: No such file or directory (needed by /bin/bash)

问题出在缺失传递性依赖项。更多细节详见我们的第三次尝试

xargsawk竟是免费附赠的?

图0:如何将我首个发布的Docker镜像体积缩减40%——关于将Shell脚本Docker化的经验分享

事实证明,Alpine镜像默认已包含xargsawk。可通过以下命令验证:

docker run -it alpine /bin/sh -c "awk --help"
docker run -it alpine /bin/sh -c "xargs --help"

新手失误。让我们从Dockerfile中移除gawkfindutils

# First stage: Install packages
FROM alpine:3.18 as builder

RUN apk add --no-cache 
    bash 
    coreutils 
    git 
    ncurses 
    fzf

# Copy only the ugit script into the container at /app
WORKDIR /app
COPY ugit .

# Set permissions and move the script to path
RUN chmod +x ugit && mv ugit /usr/local/bin/

# Second stage: Copy only necessary binaries and their dependencies
FROM alpine:3.18

COPY --from=builder /usr/local/bin/ugit /usr/bin/
COPY --from=builder /usr/bin/git /usr/bin/
COPY --from=builder /usr/bin/fzf /usr/bin/
COPY --from=builder /usr/bin/tput /usr/bin/
COPY --from=builder /usr/bin/cut /usr/bin/
COPY --from=builder /usr/bin/tr /usr/bin/
COPY --from=builder /usr/bin/nl /usr/bin/
COPY --from=builder /usr/bin/env /bin/
COPY --from=builder /bin/bash /bin/

WORKDIR /app
# Run ugit when the container launches
CMD ["ugit"]

镜像大小现已缩减至20 MB。我们正逐步接近目标。不过 ugit 仍无法运行。

第三次尝试 – 在第二阶段使用scratch

这是多数人不敢尝试的部分。它令人胆怯且需要极大决心。我起初也心存顾虑,因为深知所有内容都可能被清空 🙃。

SCRATCH Docker镜像本质上是空文件系统,完全没有任何内容。若需尝试SCRATCH镜像,可参考Docker Hub的官方说明

你只需要知道,所有内容都必须由我们自行整合。让我们保持专注,将alpine替换为scratch

# First stage: Install packages
FROM alpine:3.18 as builder

RUN apk add --no-cache 
    bash 
    coreutils 
    git 
    ncurses 
    fzf

# Copy only the ugit script into the container at /app
COPY ugit .

# Set permissions and move the script to path
RUN chmod +x ugit && mv ugit /usr/local/bin/

# Second stage: Copy only necessary binaries and their dependencies
FROM scratch

COPY --from=builder /usr/local/bin/ugit /usr/bin/
COPY --from=builder /usr/bin/git /usr/bin/
COPY --from=builder /usr/bin/fzf /usr/bin/
COPY --from=builder /usr/bin/tput /usr/bin/
COPY --from=builder /usr/bin/cut /usr/bin/
COPY --from=builder /usr/bin/tr /usr/bin/
COPY --from=builder /usr/bin/nl /usr/bin/
COPY --from=builder /usr/bin/env /bin/
COPY --from=builder /bin/bash /bin/

WORKDIR /app
# Run ugit when the container launches
CMD ["ugit"]

此举将镜像大小缩减至12.4MB,足足减少了60%??难道我们自食其果了吗?现在尝试运行ugit。

$ docker run --rm -it -v $(pwd):/app ugit-a3
exec /usr/bin/ugit: no such file or directory

$ docker run --rm -it --entrypoint /bin/bash ugit-a4
exec /bin/bash: no such file or directory

结果发现,由于未打包依赖项,导致bash二进制文件无法运行。让我们看看如何解决。

识别传递性依赖

现在该谈谈传递性依赖了。我们的脚本依赖于gittputbash等二进制文件;而这些工具本身可能还有依赖项。

这些依赖在技术上称为共享库。共享库是 .so 文件(Windows 中为 .dll,OS X 中为 .dylib)。ldd 是识别这些依赖的绝佳工具,它能列出二进制文件执行所需的所有库。例如在全新Alpine容器中运行ldd /bin/bash,将得到如下输出:

/ # ldd /bin/ls
	/lib/ld-musl-aarch64.so.1 (0xffff8c9c0000)
	libc.musl-aarch64.so.1 => /lib/ld-musl-aarch64.so.1 (0xffff8c9c0000)
/ # ldd /bin/bash
	/lib/ld-musl-aarch64.so.1 (0xffffb905a000)
	libreadline.so.8 => /usr/lib/libreadline.so.8 (0xffffb8f06000)
	libc.musl-aarch64.so.1 => /lib/ld-musl-aarch64.so.1 (0xffffb905a000)
	libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0xffffb8e95000)

共享库基础知识

  • 每个库都有一个_soname_,即库文件名去除版本号的部分。其前缀为lib,后缀为.so。例如,libpcre2-8.so.0 的 soname 为 libpcre2-8.so
  • 完整路径的库文件名包含其所在目录,例如 /usr/lib/libpcre2-8.so.0
  • 每个库都有一个真实名称,即包含版本号的库文件名。例如,libpcre2-8.so.0.3.6 可能就是一个真实名称。
  • 十六进制数表示该库在内存中加载的基础地址。这对我们没有用处,我们只关注库名称。现在为所有需要运行 ugit 的二进制文件完成此操作。
  • =>符号右侧的路径指向该共享库的实际路径。
  • libc开头的库名代表该架构的C库。还记得我们遇到的“exec /bin/bash: no such file or directory”错误吗?原因就在于此——我们未随系统分发该架构的C库。

以下摘自道格拉斯·克雷格关于共享库版本的文章,其中概述了共享库的原理。若需深入了解共享库,请阅读全文。

共享库的特性在于:只需编译一次,将其安装到文件系统中的共享位置(Linux系统通常为/usr/lib),任何依赖该共享库的项目即可直接使用这个已编译好的共享库。

大多数Linux发行版通过分发流行库的二进制包进一步缩短编译时间——这些包由发行版的打包系统预先编译完成。安装包时,您只需下载(理想情况下已签名的)编译库副本并将其放置到共享位置,全程无需调用编译器(或生成该库的任何构建链组件)。

我们采用类似方法识别运行 ugit 所需所有二进制文件的独特依赖项。

ℹ️ 具体实现方式是执行类似命令:for cmd in /bin/*; do echo $cmd; ldd $cmd; done

# copy lib files
COPY --from=ugit-ops /usr/lib/libncursesw.so.6 /usr/lib/
COPY --from=ugit-ops /usr/lib/libncursesw.so.6.4 /usr/lib/
COPY --from=ugit-ops /usr/lib/libpcre* /usr/lib/
COPY --from=ugit-ops /usr/lib/libreadline* /usr/lib/

COPY --from=ugit-ops /lib/libacl.so.1 /lib/
COPY --from=ugit-ops /lib/libattr.so.1 /lib/
COPY --from=ugit-ops /lib/libc.musl-* /lib/
COPY --from=ugit-ops /lib/ld-musl-* /lib/
COPY --from=ugit-ops /lib/libutmps.so.0.1 /lib/
COPY --from=ugit-ops /lib/libskarnet.so.2.13 /lib/
COPY --from=ugit-ops /lib/libz.so.1 /lib/

请注意,我们从构建器镜像中复制了 libc.musl-*ld-musl-*。这是因为这些库的构建依赖于主机机器的架构。

shebang #! 注释毫无意义

查看 ugit 的第一行代码,可见 shebang 注释 #!/usr/bin/env bash。在编写shell脚本时,使用env被视为良好实践,用于告知操作系统应使用哪个shell解释器运行脚本,这在日常开发机上非常理想。Linux(及旧版macOS)默认搭载shbash,用户还常额外安装zsh等。

但由于shebang是可选的,且我们已复制了bash二进制文件,只需通过它调用脚本即可。这还能节省镜像大小的几个字节——精确来说接近1.9 MB。

# Run ugit when the container launches
CMD ["/bin/bash", "/bin/ugit"]
# or 
# ENTRYPOINT ["/bin/bash", "/bin/ugit"]

解析 terminfo 数据库

ugit 的彩色输出得益于 tput 工具。我们加载一个仅含 bash 的精简 Alpine 镜像,进入 /etc/terminfo 目录。该目录存储着终端信息数据库。

37a1a77f70ed:/app# cd /etc/terminfo/
37a1a77f70ed:/etc/terminfo# ls
a  d  g  k  l  p  r  s	t  v  x

这些字母命名的“目录”分别代表不同的$TERM类型。例如xterm即为终端类型。若在本地执行tput -T xterm colors,即可获取终端支持的颜色数——xterm应为8,而xterm-256color则为256

现在正是机会——在terminfo数据库收录的40余种终端类型中,我们只需支持其中一种。其余终端类型均可移除,此举虽仅节省97KB空间,却是清理冗余数据的必要之举。

# copy terminfo database for only xterm-256color
COPY --from=ugit-ops /etc/terminfo/x/xterm-256color /usr/share/terminfo/x/

# Gib me all the colors
ENV TERM=xterm-256color

运行ugit所需全部组件

最终生成的Docker镜像大小为17.6MB,且不存在安全漏洞(根据docker scout在本文撰写时的检测结果)。相较于首次尝试,我们成功将镜像体积缩减了40%。

最终Dockerfile如下:

FROM alpine:3.18 as ugit-ops

RUN apk add --no-cache 
    bash 
    coreutils 
    git 
    ncurses 
    curl

# Download fzf binary from GitHub, pin to 0.46.0, ugit requires minimum 0.21.0
RUN curl -L -o fzf.tar.gz https://github.com/junegunn/fzf/releases/download/0.46.0/fzf-0.46.0-linux_amd64.tar.gz && 
    tar -xzf fzf.tar.gz && 
    mv fzf /usr/bin/

# Copy only the ugit script into the container at /app
COPY ugit .

# Set permissions and move the script to path
RUN chmod +x ugit && mv ugit /usr/bin/

# Second stage: Copy only necessary binaries and their dependencies
FROM scratch

COPY --from=ugit-ops /usr/bin/ugit /bin/
COPY --from=ugit-ops /usr/bin/git /usr/bin/
COPY --from=ugit-ops /usr/bin/fzf /usr/bin/
COPY --from=ugit-ops /usr/bin/tput /usr/bin/
COPY --from=ugit-ops /usr/bin/nl /usr/bin/
COPY --from=ugit-ops /usr/bin/awk /usr/bin/
COPY --from=ugit-ops /usr/bin/xargs /usr/bin/
COPY --from=ugit-ops /usr/bin/cut /usr/bin/cut
COPY --from=ugit-ops /usr/bin/tr /usr/bin/tr
COPY --from=ugit-ops /bin/bash /bin/
COPY --from=ugit-ops /bin/sh /bin/

# copy lib files
COPY --from=ugit-ops /usr/lib/libncursesw.so.6 /usr/lib/
COPY --from=ugit-ops /usr/lib/libncursesw.so.6.4 /usr/lib/
COPY --from=ugit-ops /usr/lib/libpcre* /usr/lib/
COPY --from=ugit-ops /usr/lib/libreadline* /usr/lib/

COPY --from=ugit-ops /lib/libacl.so.1 /lib/
COPY --from=ugit-ops /lib/libattr.so.1 /lib/
COPY --from=ugit-ops /lib/libc.musl-* /lib/
COPY --from=ugit-ops /lib/ld-musl-* /lib/
COPY --from=ugit-ops /lib/libutmps.so.0.1 /lib/
COPY --from=ugit-ops /lib/libskarnet.so.2.13 /lib/
COPY --from=ugit-ops /lib/libz.so.1 /lib/

# copy terminfo database
COPY --from=ugit-ops /etc/terminfo/x/xterm-256color /usr/share/terminfo/x/

# Gib me all the colors
ENV TERM=xterm-256color

WORKDIR /app
# Run ugit when the container launches
CMD ["/bin/bash", "/bin/ugit"]

我决定将fzf版本固定为0.46.0(本文撰写时的最新版本),因为ugit运行需要至少0.21.0版本,而且我心想反正就固定到最新版本吧。

ℹ️ 执行 docker run --rm -it -v $(pwd):/app ugit 可启动 ugit 容器。请确保当前目录为 git 仓库。

最终文件系统结构如下:

Permission     UID:GID       Size  Filetree
drwxr-xr-x         0:0        0 B  ├── app
drwxr-xr-x         0:0     886 kB  ├── bin
-rwxr-xr-x         0:0     866 kB  │   ├── bash
-rwxr-xr-x         0:0      21 kB  │   └── ugit
drwxr-xr-x         0:0     1.9 MB  ├── lib
-rwxr-xr-x         0:0     658 kB  │   ├── ld-musl-aarch64.so.1
-rwxr-xr-x         0:0      67 kB  │   ├── libacl.so.1
-rwxr-xr-x         0:0      67 kB  │   ├── libattr.so.1
-rwxr-xr-x         0:0     658 kB  │   ├── libc.musl-aarch64.so.1
-rwxr-xr-x         0:0     265 kB  │   ├── libskarnet.so.2.13
-rwxr-xr-x         0:0      67 kB  │   ├── libutmps.so.0.1
-rwxr-xr-x         0:0     133 kB  │   └── libz.so.1
drwxr-xr-x         0:0      15 MB  └── usr
drwxr-xr-x         0:0      12 MB      ├── bin
-rwxr-xr-x         0:0     919 kB      │   ├── awk
-rwxr-xr-x         0:0     1.2 MB      │   ├── cut
-rwxr-xr-x      502:20     3.6 MB      │   ├── fzf
-rwxr-xr-x         0:0     3.0 MB      │   ├── git
-rwxr-xr-x         0:0     1.2 MB      │   ├── nl
-rwxr-xr-x         0:0      67 kB      │   ├── tput
-rwxr-xr-x         0:0     1.2 MB      │   ├── tr
-rwxr-xr-x         0:0     919 kB      │   └── xargs
drwxr-xr-x         0:0     2.8 MB      ├── lib
-rwxr-xr-x         0:0     396 kB      │   ├── libncursesw.so.6
-rwxr-xr-x         0:0     396 kB      │   ├── libncursesw.so.6.4
-rwxr-xr-x         0:0     592 kB      │   ├── libpcre2-8.so.0
-rwxr-xr-x         0:0     592 kB      │   ├── libpcre2-8.so.0.11.2
-rwxr-xr-x         0:0      67 kB      │   ├── libpcre2-posix.so.3
-rwxr-xr-x         0:0      67 kB      │   ├── libpcre2-posix.so.3.0.4
-rwxr-xr-x         0:0     351 kB      │   ├── libreadline.so.8
-rwxr-xr-x         0:0     351 kB      │   └── libreadline.so.8.2
drwxr-xr-x         0:0     4.0 kB      └── share
drwxr-xr-x         0:0     4.0 kB          └── terminfo
drwxr-xr-x         0:0     4.0 kB              └── x
-rw-r--r--         0:0     4.0 kB                  └── xterm-256color

这便是让我们的 shell 应用运行的全部内容。不多不少。该喝杯啤酒了?🍺

PS:最终Docker镜像压缩至16.2MB。更新版Dockerfile详见此处。为配合本文说明,我将镜像大小保留在17.6MB。

能否进一步压缩体积?

可以,但我未继续压缩有两个原因:

原因一:固定fzf的最低版本?

在编写 ugit 脚本时,其依赖的 fzf 最低版本为 0.20.0 详见。显然最新版本会比最低要求版本更大。那么我们应该固定旧版本吗?不。因为这会引入 fzf 依赖项(即 Golang)的安全漏洞。根据 docker scout quickview 报告,旧版 Golang 存在总计 66 个安全问题。这些漏洞可能影响镜像,也可能不会。但我不会冒这个风险,希望保持镜像尽可能干净。

图1:如何将我首个发布的Docker镜像体积缩减40%——关于将Shell脚本Docker化的经验分享

ℹ️ 在Alpine生态系统中,通常不建议为软件包设置最低版本限制。

原因二:使用最新bash特性?

编写ugit时,我依赖的是bash版本4.0。若升级至新版bash(如_5.0_),tr和cut命令均可被替代。但这将引发重大变更——移除这些命令虽能节省几字节空间,却可能破坏仍在使用bash 4.0的用户脚本。即便仅我一人仍在使用旧版,我的机器仍需兼容bash。

你没试过docker-slim

  • 试过,它确实缩减了镜像体积,但导致脚本因依赖缺失而失效。Slim很棒,读者不妨试试。可惜在有限时间内,我没能让它在ugit上正常工作。
  • 况且我更想亲手掌握优化技巧,而非依赖工具代劳。

你没试过docker-squash

试过了,但体积优化效果微乎其微。以下是我在Linux(AMD架构)机器上运行的squash日志(忽略镜像大小差异,因架构不同导致体积不同):

2024-01-26 19:53:30,292 root         INFO     docker-squash version 1.1.0, Docker 20.10.22, API 1.41...
2024-01-26 19:53:30,293 root         INFO     Using v2 image format
2024-01-26 19:53:30,310 root         INFO     Old image has 30 layers
2024-01-26 19:53:30,310 root         INFO     Checking if squashing is necessary...
2024-01-26 19:53:30,310 root         INFO     Attempting to squash last 30 layers...
2024-01-26 19:53:30,310 root         INFO     Saving image sha256:1b9107b09dec50bd8134a935ad58ef07deb7dc2a639804abc2cb3d47c1e74206 to /tmp/docker-squash-20kl1kbx/old directory...
2024-01-26 19:53:30,536 root         INFO     Image saved!
2024-01-26 19:53:30,537 root         INFO     Squashing image 'ugit:latest'...
2024-01-26 19:53:30,538 root         INFO     Starting squashing...
2024-01-26 19:53:30,538 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/3b03ba6aad8ab3b95e5ae7c173b5fe8980a4bb2b57e6e3c78d4c851e62befacd/layer.tar'...
2024-01-26 19:53:30,539 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/af8614dc670d37bc2b4ee235fba1fba32f79cc665e1685d17bc09103206add88/layer.tar'...
2024-01-26 19:53:30,541 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/fdfcf6e294b868ac5c9bb79f479da7365f074a24932d35cf88e6ee87313be39f/layer.tar'...
2024-01-26 19:53:30,542 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/fea7bea22fc0e61f8b3bb33795946844efca8e6e2ece2d4cfb5d9d5dc50fa29c/layer.tar'...
2024-01-26 19:53:30,543 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/c6b22f335e663d959cf9ecb676d297de3b818c608c10811f857fbd0950d39126/layer.tar'...
2024-01-26 19:53:30,543 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/482d23995758cbffbedc338a3be0325106fc6172a7c54d23a22e8a1d585d2b9e/layer.tar'...
2024-01-26 19:53:30,545 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/3b9a86d49326da225c5b1c85e01764d210c15bec579c5af34f0fc171127f5b5f/layer.tar'...
2024-01-26 19:53:30,546 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/0ca82f27b278a4c9bb0bfe1ff3f38716051fb4849374eec8925f48efe436009b/layer.tar'...
2024-01-26 19:53:30,546 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/3cca8b35bff33c3f212c956b4c71dd419bd86c146ec38afdcdd5a0288d1659b6/layer.tar'...
2024-01-26 19:53:30,547 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/81dfb23402548ba22dff2162d2c4291d16e3553a3fc47b761d21013316571a82/layer.tar'...
2024-01-26 19:53:30,548 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/273d7b9c894ca049315a77ce845423f819ee684544f171eaf6b8689c77f6ad05/layer.tar'...
2024-01-26 19:53:30,550 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/27be6a1ed5a8507dc43bd30cb56ddddc9c0c9d52b6806eec8037f1fa09395552/layer.tar'...
2024-01-26 19:53:30,551 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/d5f577b8ef6ec1875b1d986ca79da0339c1d228cc31bce4d42988556135c5909/layer.tar'...
2024-01-26 19:53:30,551 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/f292c627dcee37256b008f33c3bc91c65463e7b8a6758306c130f53d80b045e5/layer.tar'...
2024-01-26 19:53:30,553 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/41a4f2afaf2abd9098d62d9b4d5c6ee88c2d9478f48be9c8f407a81c7b207d6e/layer.tar'...
2024-01-26 19:53:30,555 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/5dc1b8c25faa63c58a7905850043c14c59dced4746d7d34453edad7b86956849/layer.tar'...
2024-01-26 19:53:30,557 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/f91a2a9df95e299850a3c512d292167e12f7df86dfc6a598279d35db4d804b7d/layer.tar'...
2024-01-26 19:53:30,560 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/72447920860e9e88f3680623d2ac1d10f78111f16c3563b43009852d704d7515/layer.tar'...
2024-01-26 19:53:30,563 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/cc9b36b470edfc560a35e67fa7d3adbcf3fe91acfaf82cd484080418a1168c7f/layer.tar'...
2024-01-26 19:53:30,566 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/05a8e77bfb6dd759bacb2ba9c1505bc66979885591d623d83ce58cbe0047a539/layer.tar'...
2024-01-26 19:53:30,568 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/a3452a5b414ce5be1c02a8d88374419f8de637552486ca47d6d810adc351386e/layer.tar'...
2024-01-26 19:53:30,570 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/38af27767b34ce2676958ed305d78508fe745597b548d65b4a5532481ffd3296/layer.tar'...
2024-01-26 19:53:30,570 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/c2e188390cb6b51470e879b62e2686add024114bcb76eb163053382cb424928a/layer.tar'...
2024-01-26 19:53:30,574 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/d03cc62007f0fa8d1c09d0f34f5e74e0e6f65f48c9a53177b189c57f47be8074/layer.tar'...
2024-01-26 19:53:30,578 root         INFO     Squashing file '/tmp/docker-squash-20kl1kbx/old/fa90033ac9d3c9085ba57fc406aed549a0dc14823c8f63f6358c9557b8f0a9ea/layer.tar'...
2024-01-26 19:53:30,578 root         INFO     Squashing finished!
2024-01-26 19:53:30,608 root         INFO     Original image size: 17.34 MB
2024-01-26 19:53:30,608 root         INFO     Squashed image size: 17.29 MB
2024-01-26 19:53:30,608 root         INFO     Image size decreased by 0.32 %
2024-01-26 19:53:30,608 root         INFO     New squashed image ID is 2c697df9cb2d9fd5f4534e6358d68f1e1a04bb5b6d27087f894dbf492964b791
2024-01-26 19:53:30,741 root         INFO     Image registered in Docker daemon as ugit-squashed:squashed
2024-01-26 19:53:30,746 root         INFO     Done

心得体会

  • Linux 令人惊叹。源自它的每个设计决策、每款工具的灵感都继承了这份卓越。
  • 切勿回避深入探索微优化这个🐇兔子洞。你将收获良多,总有新知待发现。

鸣谢

  • 衷心感谢 ldd 的作者,以及 dockeralpine Linux 社区的每一位成员。
  • 感谢 developersIndia Discord 频道的同仁们提供的建议与帮助。
  • dive 帮助我可视化镜像层级。

参考资源

希望大家今天读到有趣的内容。下次再见,祝编程愉快!离开前请记得给ugit点个星标,并执行docker pull?🧡

本文文字及图片出自 How I reduced the size of my very first published docker image by 40% - A lesson in dockerizing shell scripts

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号