我这辈子亲手重建过不少 Linux 机器,剩下的也都用 Ansible 和 shell 脚本自动化掉了。所以当人们 一再告诉我 NixOS 才是终点——说你不再配置一台机器,而是描述它——我就想知道这是真有 其事,还是一种非常有说服力的宗教。我拿一台闲置的机器和一个容器试了一个月。这是一份初体验的 实地报告,不是教程:它的吸引力到底是什么、配置长什么样、它在哪里值回票价、以及它在哪里让我 想把笔记本扔出去。
吸引力:一个文件,一台能被精确重建的机器
声明式配置的承诺,一句话就能说清,但很难被高估。在 NixOS 上,整台系统——软件包、服务、
用户、内核参数、防火墙、你的 shell——都活在少数几个声明式文件里,主要是
/etc/nixos/configuration.nix。你不去 apt install 东西。你描述这台机器,跑一条命令,
系统就被构建成与之相符的样子。
这和一份配置脚本的区别,正是可复现构建重要的原因:Nix 在隔离的沙箱里,从一份描述构建
每一个包,并把结果按「其全部输入哈希而成」的路径存进 /nix/store。两台要求同一份 derivation
的机器,拿到的是同样的字节——而不是「大概版本相同」。那个按哈希寻址的 store 是全部的诀窍。
它也是第二条性质——原子回滚——几乎免费的原因。
每次你运行 nixos-rebuild,Nix 都会构建一代新的、不可变的系统代际(generation),并把它
加进启动菜单。如果新一代坏了——某次升级把网络搞瘫、某个服务起不来——你不用在凌晨两点去调它。
你在引导器里挑上一代,或者直接跑:
sudo nixos-rebuild switch --rollback
一条命令,几秒钟回到已知良好的系统。没有漂移要收敛,因为从来就没有漂移:运行中的系统就是 构建产物。这一点是真正改变了我对机器看法的部分。
配置到底长什么样
真正让我相信这不是纸上谈兵的,是一份真实配置有多短。下面是我跑的那台机器删减后的版本—— 一台带 SSH 加固、非 root 用户和防火墙的 Web 服务器,大约三十行:
# /etc/nixos/configuration.nix
{ config, pkgs, ... }:
{
boot.loader.systemd-boot.enable = true;
networking.hostName = "nixbox";
time.timeZone = "Asia/Shanghai";
# 用户与 SSH:只用密钥,禁用密码登录。
users.users.deepseek = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3Nza… deepseek@laptop"
];
};
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
};
# 一个服务,是被声明的,而不是被安装的。
services.nginx = {
enable = true;
virtualHosts."blog.awin-x.top" = {
root = "/srv/www/blog";
locations."/" = { tryFiles = "$uri $uri/ =404"; };
};
};
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
# 这台机器将拥有的确切软件包集合。
environment.systemPackages = with pkgs; [
git vim tmux htop curl jq
];
system.stateVersion = "24.05";
}
你用一条命令施加它,而且因为跑第二遍什么都不变,它感觉就像是 单台服务器上的 Ansible里我试图从 playbook 拿到的东西, 诚实版本的样子:
sudo nixos-rebuild switch # 构建并激活新一代
sudo nixos-rebuild test # 激活它,但不设为默认启动项
sudo nixos-rebuild switch --rollback # 撤销
吸引力全在这里了。它确实是一种拥有机器的不同方式,而第一周它感觉像是超能力。
学习曲线:Nix 语言、flakes,以及文档
然后现实来了,而且是三波一起到的。
Nix 语言。 Nix 是一门小巧、惰性、纯函数式的语言,本身不难——但它陌生。配置值是函数、
overlay 和属性集(attribute set)。with pkgs; 把名字带入作用域。你的第一周会花在读别人的
配置、复制你并不完全理解的片段上,这没问题,直到某个片段和另一个冲突,你才不得不真正搞懂为
什么。这门语言是声明式配置的代价,你没法不付。
Flakes。 Nix flakes 是描述输入的现代、锁定、自包含方式:与其跟着 nixpkgs-unstable
听天由命,一份 flake.nix 会把每个依赖锁进 flake.lock,就像 lockfile 本该做的那样。它们是
未来,对可复现性显然更好——但也撕裂了生态:你能找到的文档和示例有一半是 channel 时代的,
另一半是 flake 时代的,而没有任何东西告诉你哪个是哪个。一份最小的 flake.nix 长这样:
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }: {
nixosConfigurations.nixbox = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./configuration.nix ];
};
};
}
有了它,nixos-rebuild switch --flake .#nixbox 就能用锁在 flake.lock 里的输入构建整台系统。
很棒。但现在「channels 还是 flakes」成了你必须做的决定,而你连两者都还没搞懂,很多教程还会
悄悄替你选一边。
文档。 这里我得直说:文档是最弱的一环。手册是完整的,但读不下去。选项可搜索,这救了你—— 从描述构建机器是一条生命线——但为什么常常缺失,报错 信息出了名地没用,而真正好的解释活在一些博客和论坛帖子里,而不是任何官方材料里。对这么聪明 的一套系统,文档却像是个事后补丁。
NixOS 的用武之地,和它硌人的地方
服务器与 CI:这才是甜区
在服务器上,NixOS 近乎理想。配置就是文档,构建可复现,一次重建就是一个回滚目标。你把同一
代际启动到一台全新的 VPS 上,它和你测过的那个字节一致——没有「在我机器上能跑」。对 CI 尤其
是一种安静的超能力:nix develop 让每个任务拿到完全相同的工具链,于是构建为什么慢
不再有部分是谜,而「我机器上通过了」也失去了借口。
开发环境:被人低估的最好部分
在所有这些挫败之后,我仍会继续用的唯一功能就是 nix develop。一个文件锁定你的编译器、语言
运行时、linter 和系统库,任何同事或 CI runner 都能用一条命令拿到同样的环境。如果你曾伺候过一
台两个服务对同一个原生库版本意见不一的共享机器——就是我在不用 Docker 的自托管
里描述过的问题——这是我找到的最干净的解法,而且完全不需要你在 NixOS 上全押。
硌人的地方:隐藏的魔法与被打破的反射
但少了另一半,这份实地报告就不诚实。
- 隐藏的魔法。 透明在顶层是真的,在底下却是浑浊的。当某样东西表现异常时,原因往往埋在一个
你没写的 overlay、某个模块的默认值,或某个
lib函数里。你在调试一个生成系统的函数式程 序,而不去深挖,你就看不到那个被生成的系统。 - 调试方式不同,而且一开始更糟。 坏配置没有
systemctl restart能救。错误来自求值 (evaluation)而不是运行时,而 Nix 的报错是我这些年用过的最没帮助的。你练出的本事是真的,但 第一个月疼得不成比例。 - 生态缺口。 Ubuntu 给所有东西都准备了
.deb和一个 Stack Overflow 答案。NixOS 有的是nixpkgs里的一个 derivation——前提是有人写过。没人写过时,你就得自己打包——那是一门真本 事,不是五分钟绕个路。有些二进制还假设一个 FHS 文件系统,而 NixOS 故意不提供;nix-ld、 一个buildFHSUserEnv,或者一个 wrapper 是解法,而这三个你都会遇到。 - 「直接改文件」的反射。 这个没人提醒过你。我有十年在凌晨三点改
/etc/nginx/nginx.conf的经历,而且它管用。NixOS 故意禁止这个反射——对 store 或生成文件的 改动会在下次 switch 时被抹掉——但这个反射深深刻在我手指里,改掉它花了好几周。当你和这个模型 对着干,NixOS 会礼貌地惩罚你。
诚实的建议
NixOS 不是一次周末切换,任何说它是的人,都已经忘了自己的第一个月。
该用它,如果你有耐心、你看重精确重建与回滚一台机器,而且你愿意在学习和 flakes 上、以及 在写出好配置之前读大量配置这件事上,付出一笔实打实的前置成本。回报是真的:可复现构建、原子回 滚,以及一台你描述而非伺候的系统。对服务器、CI 和锁定的开发环境,这是我用过的最强的答案。
该等一等,如果你这周就需要机器能用,如果你的技术栈依赖没人打包过的软件,或者如果你知道自
己会习惯性地伸手去 vi /etc/...。先在一台虚拟机或闲置机器上试试——我就是这么做的,而这也是
唯一诚实地判断这笔交易对你不划算的办法。
我自己的打算:在可复现性值回票价的那几台机器上留着 NixOS,其他地方继续用 nix develop。一台
机器应该是你能够重建的描述,而不是你维护的宠物——这个想法是对的。我只是不假装通往它的路很
短。