Nix 是比 Docker 的镜像构建器更好的 Docker 镜像构建器

Nix vs. Docker 作为镜像构建器

  • 基于 Nix 的构建因其声明式、细粒度的依赖管理以及比典型 Dockerfile 更强的可复现性而受到称赞。
  • Dockerfile 则因存在难以察觉的非确定性而受到批评(浮动标签、未固定版本的 apt-get、对分支进行 git clone),不过也有人认为,只要纪律性地使用 digest、快照和镜像源,Docker 也可以做到可复现。
  • 一些人指出,Docker 的层模型是分层的,并且围绕命令式构建步骤组织;而 Nix 会将依赖图映射为层,从而能够生成更可复用、按内容寻址的层。

分层、Snapshotter 和工具

  • 将 Nix store 路径天真地映射为层时,Docker 的 128 层限制会非常棘手;人们会使用启发式方法或分组处理,但这会损害缓存复用。
  • nix-snapshotter 将 Nix 与 containerd 集成:容器可直接从 Nix store 挂载;docker pull 变成 Nix substitution,避免了 tar 包重复和层数限制。它在基于 containerd 的集群上部署很直接,但在托管的 k8s 上则更棘手。
  • nix2container 和 Nix 的 buildLayeredImage 通过流式传输层并将其存储在 Nix cache 中,以及允许用户显式选择每一层包含的内容,从而改进了缓存和 registry 上传。

镜像大小与优化

  • 有人反馈 Nix 构建的镜像意外地很大(例如大型 Nix 基础镜像、完整 JDK、包含所有语言环境的 glibc)。
  • 也有人展示,只要仔细使用 musl、无头 JRE、最小 JRE,以及通过 override 去掉未使用功能,Nix 镜像可以具有竞争力,甚至更小;buildLayeredImage 还能让大型依赖(glibc、JDK)在多个镜像之间共享。

开发体验与复杂性

  • 观点分歧明显:拥护者认为一个 flake 就能描述构建、dev shell、Docker 镜像、NixOS 模块和测试;怀疑者则觉得 flakes 和 nix 语言难学、过度抽象且文档不足。
  • 常见抱怨包括:错误信息不透明、“dark corners”、跨编译学习曲线陡峭,以及庞大而定制的 flake 架构让新手望而却步。
  • 文中提到了多种缓解复杂性的尝试(flake 结构模式、更高层框架、像 flox 这样的工具),但普遍共识是 DX 仍然是主要障碍。

跨平台与生态系统问题

  • macOS/darwin 用户面临缓慢或脆弱的跨编译、缺失的 Hydra 缓存,以及复杂的 Docker 集成;许多人最终使用远程或 VM Linux 构建器。
  • Guix 被提及为类似的函数式替代方案,并带有 guix pack -f docker,但其包更新更慢,审查延迟也更高。
  • 对于简单用例(例如 Go/Java 服务),一些人认为语言专用的镜像构建器(ko、Jib、Bazel rules_oci)比 Nix 的复杂性更可取。