将 shell 脚本 docker 化的一课

为了把一个 500 行 bash 工具的 Docker 镜像尽量缩小,讨论集中在镜像最小化与长期可维护性之间的权衡。评论者警告说,把二进制文件和共享库手工复制到 `scratch` 或基于 Alpine 的镜像中,可能会制造出脆弱、难以调试的容器;随着操作系统更新,它们还可能出问题,而带来的空间节省相较于一个直接使用 30 MB 左右基础镜像的方案并不算大。讨论随后扩展到:在什么情况下把简单 shell 脚本容器化是合理的,并涉及静态二进制文件、BusyBox、Nix 和传统包管理器等替代方案,以及用于分析和优化镜像的工具。

镜像大小 vs. 复杂性

  • 许多人认为,最初约 31 MB 的镜像已经“足够小”了;对于大多数生产用途而言,把它进一步缩到约 17 MB 并不值得增加额外复杂性。
  • 不少评论者宁愿接受一个 30–40 MB 的镜像,也不愿维护一个脆弱、过度调优的 Dockerfile。
  • 也有人把这种优化当作学习工具,乐于探索运行该脚本所需的最小条件。

复制二进制文件和共享库

  • 对于直接把一个 Alpine 镜像中的二进制文件和 .so 文件复制到另一个镜像(或 scratch)里,并覆盖系统的 lib/bin 路径,批评非常强烈。
  • 担忧包括:版本偏移、损坏的符号链接、随着基础镜像演进而出现的微妙不兼容,以及这种脆弱镜像可能在之后悄悄失效。
  • 有些人认为,如果源和目标使用相同的基础版本,这个问题可以在一定程度上缓解;但也有人把这种做法看作是对“包管理器”的一种“反包管理器”式重造。

Alpine、可复现性与固定版本

  • Alpine 被描述为对版本固定和长期可复现构建不友好:包和索引消失得很快。
  • 建议:如果你需要可复现、对缓存友好的 Docker 构建,就不要使用 Alpine;基于快照的 Debian 镜像被提到是更有前景的方向。

更小镜像的替代方案

  • 建议包括:
    • 使用静态二进制文件或 BusyBox 构建,而不是复制许多个别工具。
    • 使用 Nix 以声明式方式定义镜像;可行,而且可以非常小,但通常会拉入很大的闭包(例如,“gitMinimal” 仍然很大),并增加复杂性。
    • 如果有人愿意投入精力,可以把工具重写成一种编译型语言,用单个静态二进制文件实现。

安全、信任与审计

  • 关于“随机 shell 脚本”还是“随机 Docker 镜像”更安全的争论。
  • 脚本的优点:更容易阅读,也更容易通过像 ShellCheck 这样的工具检查。
  • 容器的优点:通过命名空间以及卷/网络控制实现隔离,并且可以借助像 Dive 这样的工具检查内容。
  • 反驳点:容器并不是强安全边界;逃逸是存在的,而审计整个镜像以及基础层并不简单。

docker 化 shell 脚本的用处

  • 有些人认为,对于一个只需要标准工具的 500 行 bash 脚本来说,容器化过于大材小用。
  • 也有人认为容器简化了依赖管理,并让工具在许多机器之间更易复现。
  • 提到的实际问题:你仍然需要一个包装器(脚本/别名/compose)来挂载工作目录,并让使用方式更符合习惯。