deploy-rs in 2026: reviving our Nix deployment tool
!Heitor Toledo Lassarote de Paula.png)
10 月 19 日,我们为 deploy-rs 打了 1.0.0 标签。这是我们在 2020 年发布的 Nix 部署工具的第一个版本化发布,也是第一个发布到 crates.io 的版本。与此同时,我们还发布了 update-daemon,这个工具可以跨多个仓库更新 flake 输入,并提交包含变更的 pull request。
在此之前的好几年里,deploy-rs 几乎没得到我们任何关注。随着时间推移,我们大部分工程时间都投入到了客户项目中,内部工具分到的时间越来越少。deploy-rs 对我们自己的部署来说一直够用,所以待办事项清单在悄无声息中越积越长。
今年我们处理了这些积压事项。我们整理了未解决的 issue,审查并合并了影响最大的 pull request,自己修复了一些 bug,并发布了一个版本。这篇文章会介绍 deploy-rs 和 Nix 自 2020 年以来的变化、我们在 Serokell 如何使用 deploy-rs,以及我们接下来计划做些什么。
1.0.0 版本汇集了六年的修复,让你可以锁定版本
如果你还没用过:deploy-rs 会把 flake 中定义的 Nix profile 部署到远程机器上。一个 profile 可以是 NixOS 系统、一个应用或一个用户 profile;如果部署失败,或者目标机器无法确认部署成功,magic rollback 会恢复之前的 profile。
直到今年,deploy-rs 都没有版本号,唯一的使用方式就是跟随 master 分支。现在有了一个可以固定在 flake inputs 中的 git 标签,以及面向未来版本的 changelog:
inputs.deploy-rs.url = "github:serokell/deploy-rs/v1.0.0";
你也可以从 crates.io 安装 deploy 和 activate 二进制文件:
cargo install deploy-rs --locked
它需要 Rust 1.85 或更新版本,部署时仍然需要 Nix。对于部署场景,我们仍然建议从 flake input 中获取 CLI,而不是从 crates.io 获取:你节点上的 activate 二进制文件是从那个 input 构建的,不同版本的 CLI 可能与之不匹配。这个 crate 主要在你希望把 deploy-rs 作为 Rust 库使用时才有用。请注意,crates.io 上的 0.1.2 及更早版本属于另一个项目,其作者 alianse777 慷慨地将这个名字转让给了我们;1.0.0 是我们的第一个发布。
今年的大部分变更来自外部贡献者。我们审查了这些变更,修复了一些问题,然后合并了它们。其中有一些你很可能马上就会注意到。
使用 remoteBuild 的部署现在会并行构建并推送到多台主机(#271,由 PhilTaken 贡献)。本地构建仍然按顺序执行,以避免让你的机器过载,激活也仍然是串行的;下面会详细说明。
你也可以只部署 flake 的一部分。Profile、节点和整个部署都可以放入组中,--groups 只会部署这些组(#365,由 pippaye 贡献)。新的 --test 标志工作方式类似 nixos-rebuild test:它会激活配置,但不将其设为启动默认项(#353,由 cole-h 贡献)。
Magic rollback 不再回滚成功的部署。由于竞态条件,快速确认可能会被错过,然后 deploy-rs 会回滚一个已经确认过的部署。davidvornholt 在 #385 中修复了这个问题。
其余的都是较小的改动,大多数是为了让失败更容易处理。当激活失败时,deploy-rs 现在会立即停止等待,而不是一直等到超时(#373,由 paulbertin 贡献)。Nix 命令失败时会显示该命令及其错误输出(#310)。进度条可以通过 --no-progress 关闭,并且在 CI 日志中或当你把输出通过管道传给 nix-output-monitor 时会自动关闭(#391),来自目标的输出会用 📠 标记,以区别于本地输出(#401)。Flake 检查每个 flake 只运行一次,而不是每个目标运行一次(#330,由 weriomat 贡献),并且支持从内容寻址派生构建的 profile(#375,由 poly2it 贡献)。完整列表见 changelog 和 git 历史。
早年从未公布过的功能
2020 年那篇文章之后,有几个较大的功能上线了,但我们一直没对外说过。自 2021 年起,你可以用一条命令部署多个节点,并且当后面的节点失败时,已成功的节点默认会回滚(#80,由 ysndr 贡献);还可以把 deploy-rs 当作 Rust 库使用(#104,由 nrdxp 贡献)。remoteBuild 本身于 2022 年加入(#175,由 PhilTaken 贡献),它利用目标主机的 substituters 在目标主机上构建 profile。2023 年,activate.darwin 让使用 nix-darwin 部署到 macOS 无需自定义激活脚本即可工作(#213);另有一处修复让用户 profile 部署在较新 Nix 版本迁移了用户 profile 之后仍能正常工作(#231)。2024 年,n-hass 加入了 interactiveSudo:当你通过非 root 的 SSH 用户部署时,它会询问 sudo 密码,并保持 magic rollback 正常工作(#257)。同年,我们开始在 CI 中部署到虚拟机来测试每个 pull request(#264)。
Nix 一直在变,deploy-rs 必须跟上
deploy-rs 是 Nix 命令之上的一层薄封装,Nix 输出或行为上的每一处变化迟早都会传导到它这里。以下是 2020 年以来对 deploy-rs 用户最重要的变化。
Flakes 仍处于实验阶段。它们随 2021 年 11 月的 Nix 2.4 首次进入稳定版本,并且仍然需要 experimental-features。RFC 136 计划将其稳定化,但没有确定日期。在那之前,Nix 命令可能在不同版本之间发生变化,deploy-rs 必须跟上。
最明显的例子是 Nix 2.32(2025 年 10 月)。它改变了描述 derivation 的 JSON 格式,而 deploy-rs 会解析这个格式,随后 Nix 2.33 又改了一次。这两次,在新版本上部署都会失败,直到贡献者在 #346 和 #359 中修复。这类破坏正是即使没人要求新功能、deploy-rs 也需要维护者的主要原因。
现在也不止一个 Nix 了。Lix 从 Nix 2.18 分叉而来,于 2024 年 7 月发布首个版本;Determinate Systems 也发布了自己的 Nix 发行版。deploy-rs 并不官方支持 Lix,但已有一些用户在使用:#394 就来自一条 Lix 警告。
在 NixOS 一侧,与 deploy-rs 相邻的两个程序被重写了。负责激活 NixOS 配置的 switch-to-configuration 现在有了 Rust 版本,自 NixOS 24.11 起成为默认。deploy-rs 无需改动即可与之配合,我们的 CI 测试也覆盖了它。nixos-rebuild 被用 Python 重写,新的 nixos-rebuild-ng 自 NixOS 25.11 起成为默认。它可以通过 SSH 用 --target-host 部署单台 NixOS 机器。对于一台机器、一份系统配置的场景,这或许就够了。deploy-rs 仍然额外提供一次部署多个 profile 和节点、用户 profile,以及 magic rollback。
最后,2026 年 7 月发布的 Nix 2.35 不再总是在求值前把 flake 的源码复制到 store。使用大型仓库的 deploy-rs 用户可能会看到求值变快,不过我们还没有实测。
我们几乎用它部署自己的全部基础设施
即便在疏于维护 deploy-rs 的期间,我们也一直依赖它。我们的服务器按集群划分,每个集群各有自己的仓库,全部都用 deploy-rs 部署。我们的公司网站和若干内部工具也是如此。
其中一些仓库是公开的:
- gemini-infra,我们内部服务所用的集群。
- hackage-search,让你可以搜索整个 Hackage,运行在 hackage-search.serokell.io。
- website-deployment-example,来自我们文章 Website deployment for Runtime Verification 的示例。
nix-templates 中的 infra 模板也已经预先配置好了 deploy-rs。
服务器本身我们按常规方式部署,作为 NixOS 系统 profile。对于运行在服务器上的服务,我们最终形成了两种做法,而它们之间的差异正是我们真正要分享的一条经验。
文章标题:deploy-rs in 2026: reviving our Nix deployment tool
正文: 在第一种方式中,应用仓库定义一个包含 systemd 服务的 NixOS module,基础设施仓库将该 module 作为 flake input 导入。该服务作为服务器系统 profile 的一部分被部署,而 CI 只部署应用二进制,作为单独的 profile。其激活脚本会重启服务,随后服务会加载新的二进制。tzbot 就是这样工作的,它被部署到 gemini-infra 中的一台服务器上。缺点在于,对该 module 的任何改动,例如新增一个环境变量,都意味着要更新基础设施仓库中的 flake lock,并重新部署整个服务器。
在第二种方式中,应用仓库部署整个服务。它从 NixOS module 构建 systemd unit,CI 用 deploy-rs 将该 unit 的 closure 作为用户级服务部署。激活脚本会把该 unit 链接到 systemd 目录中并重启它。serokell.nix 有一个构建该激活脚本的 helper,应用仓库将其包装在 deploy-rs profile 中。在这种方式下,修改设置完全不需要改动服务器配置。website-deployment-example 是一个公开示例,我们的 Runtime Verification 文章详细介绍了这套配置。
第二种方式避免了重新部署服务器,代价是每个应用仓库需要更多配置。deploy-rs 同时支持这两种方式,因为它可以部署任何 Nix profile,而不只是 NixOS 系统。
我们会持续审查和合并,但不承诺时间表
截至撰写本文时,deploy-rs 有 40 个未关闭的 pull request 和 95 个未关闭的 issue,我们不会很快清理完这些积压。我每周都会留出时间审查 pull request 并分诊新的 issue。我们能做多少额外工作取决于我们的客户项目,因此我们不会为功能设定日期。
接下来我们想处理四个问题。我们根据人们提出的频率以及看起来所需的工作量挑选了它们。
最受欢迎的功能是并行部署(#46)。使用 remoteBuild 的构建已经并行运行,但激活仍然一次只处理一个 profile。从 Mac 部署 NixOS 配置,或反过来,比听起来更难(#216):即使使用 --remote-build,flake 检查也在本地运行,而远程运行它们会带来大量复杂性。在此之前,--remote-build --skip-checks 可以绕过这个问题。
还有两个较小的问题会让日常部署不那么烦人。deploy-rs 每次部署会打开多个 SSH 连接,如果你的密钥需要 PIN 或 passphrase,你就得一遍又一遍地输入(#225)。我自己就遇到过这个问题。能看到新旧配置之间的 diff 也会很有帮助(#356)。
如果其中某个问题对你很重要,或者缺少其他功能,请在 issue tracker 中说明。也欢迎 pull request;小而聚焦的 PR 对我们来说审查最快。