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 的复杂性更可取。