NixOS 26.05 深度解析:声明式系统的终极形态——从 systemd Stage 1 到 14 万包仓库,程序员为什么要认真对待这个「异类」发行版
前言:一个让 Docker 都感到不安的发行版
2026 年 5 月 30 日,NixOS 项目发布了代号为 "Yarara" 的 26.05 版本。这个版本带来了 2842 位贡献者的 59703 次提交,新增 20442 个软件包,更新 20641 个,移除 17532 个过时包。
这些数字本身已经足够震撼,但真正让这个版本成为里程碑的,是三个足以改变 Linux 系统管理范式的变化:
- systemd Stage 1 成为默认启动方式——彻底告别脚本式 initrd
- system.nix 全新入口文件——声明式配置不再依赖 nix-channel
- systemd-nspawn 容器化测试后端——集成测试速度提升数十倍
如果你是一个程序员,你可能已经听说过 NixOS,但可能觉得它「太学术」、「太难学」、「跟我的工作没关系」。这篇文章会改变你的看法。
我们将从底层原理到生产实战,从 Nix 语言的函数式设计到 Flakes 的现代工作流,彻底拆解这个 Linux 世界中最独特的存在。读完之后,你会理解为什么越来越多的顶级工程团队——从 Meta 到 Stripe,从 Target 到 Replicate——正在将核心基础设施迁移到 NixOS 上。
第一章:NixOS 到底是什么?为什么它与众不同?
1.1 传统 Linux 发行版的困境
在传统 Linux 发行版(Ubuntu、CentOS、Arch)中,软件包管理遵循一个基本模型:安装、升级、卸载,这些操作会修改系统全局状态。这个模型存在几个根本性问题:
依赖地狱(Dependency Hell):当你安装包 A 需要库 libfoo v1.2,而包 B 需要 libfoo v1.3 时,系统就陷入了冲突。传统的解决方案是多版本共存(如 Python 2 和 Python 3 并存),但这只是临时补丁,无法系统性地解决问题。
配置漂移(Configuration Drift):一台运行了两年的服务器,你还能准确说出它和初始安装有什么区别吗?手动修改的配置文件、临时安装的调试工具、为了修 bug 而降级的某个包——这些变更的累积让系统状态变得不可预测。
不可复现(Non-reproducible):你在自己的笔记本上开发和测试通过的代码,部署到服务器上却出问题了。两台「一模一样」的 Ubuntu 22.04 机器,可能因为安装顺序不同、手动配置差异,导致完全不同的行为。
1.2 NixOS 的声明式革命
NixOS 用一个激进的想法解决了这些问题:整个操作系统由一个配置文件完全描述。
# configuration.nix — 这一个文件就是你的整个系统
{ config, pkgs, ... }:
{
# 硬件配置
boot.loader.systemd-boot.enable = true;
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
# 网络
networking.hostName = "my-server";
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
# 软件
environment.systemPackages = with pkgs; [
vim git curl jq
nodejs_22 python3
];
# 服务
services.nginx.enable = true;
services.postgresql.enable = true;
# 用户
users.users.developer = {
isNormalUser = true;
extraGroups = [ "wheel" "docker" ];
};
}
这个文件描述的不是一个「安装脚本」,而是系统的期望状态。当你运行 nixos-rebuild switch 时,NixOS 会:
- 解析配置文件,计算出需要哪些包和服务
- 只构建和部署发生变化的部分
- 创建一个新的系统「世代(generation)」
- 原子性地切换到新世代
如果新配置有问题,你可以随时回滚到上一个世代——不需要重装系统,不需要手动撤销修改,一条命令搞定:
# 回滚到上一个世代
sudo nixos-rebuild switch --rollback
# 或者在启动时选择历史世代
# systemd-boot 菜单会列出所有历史世代
1.3 Nix:函数式包管理器
NixOS 的底层是 Nix 包管理器,它的设计哲学来自函数式编程:
- 纯函数构建:每个包的构建过程是一个纯函数,输入(源码、依赖、构建参数)确定,输出就确定
- 不可变存储:所有包存储在
/nix/store中,路径包含哈希值,如/nix/store/b6gvzjyb2pg0...-gcc-13.3.0 - 垃圾回收:不再被引用的包可以安全删除,不会影响其他包
# 一个 Nix 包的定义示例
{ lib, stdenv, fetchFromGitHub, rustPlatform, pkg-config, openssl }:
rustPlatform.buildRustPackage rec {
pname = "my-tool";
version = "1.0.0";
src = fetchFromGitHub {
owner = "myorg";
repo = "my-tool";
rev = "v${version}";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
cargoHash = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";
nativeBuildInputs = [ pkg-config ];
buildInputs = [ openssl ];
meta = with lib; {
description = "A cool developer tool";
license = licenses.mit;
maintainers = [ maintainers.myname ];
};
}
关键点在于 hash 字段。这个哈希值锁定了源码的确切内容,加上 cargoHash 锁定了 Rust 依赖,加上 buildInputs 锁定了系统依赖——这意味着任何人、在任何时间、在任何机器上构建这个包,都会得到完全相同的结果。
这就是可复现构建(Reproducible Builds)——不是一种理想,而是一种工程约束。
第二章:NixOS 26.05 核心变化深度拆解
2.1 systemd Stage 1:启动流程的现代化革命
这是 26.05 中最重大的架构变化。要理解它的意义,我们需要先了解 Linux 启动流程。
传统 NixOS 启动流程:
BIOS/UEFI → bootloader → kernel → initrd (Stage 1) → init (Stage 2)
Stage 1(initrd)是在内核加载后、真正的根文件系统挂载前运行的微型环境。它的主要职责是:
- 加载必要的内核模块
- 解密 LUKS 加密分区
- 挂载根文件系统
- 将控制权移交给 Stage 2(systemd)
在旧版 NixOS 中,Stage 1 是通过一组 Bash 脚本实现的。这些脚本虽然能工作,但存在几个问题:
- 难以调试:脚本式启动在出错时很难定位问题
- 缺乏依赖管理:脚本中的操作顺序是硬编码的,无法并行化
- 与现代 Linux 生态脱节:systemd 已经成为 Linux 系统管理的事实标准,但 Stage 1 却游离在外
NixOS 26.05 的解决方案:
从 26.05 开始,Stage 1 默认基于 systemd 实现。这意味着:
# 这一行现在是默认行为(26.05+)
boot.initrd.systemd.enable = true; # 默认为 true
systemd Stage 1 带来的好处:
- 原生 LUKS2 支持:systemd-cryptsetup 提供更可靠的加密卷处理
- 并行设备发现:systemd-udev 的事件驱动模型替代了顺序扫描
- 统一的日志系统:启动过程中的所有日志都通过 journald 收集,可以用
journalctl -b查看完整的 initrd 日志 - 更好的错误处理:systemd 的单元文件有明确的依赖关系和超时机制
迁移注意事项:
对于使用 LUKS 加密的用户,需要注意一个重要变化:
# 旧配置(可能需要修改)
fileSystems."/" = {
device = "/dev/mapper/cryptroot"; # 确保这个名字与 LUKS 设备名一致
fsType = "ext4";
};
# 如果你有 LVM on LUKS 的复杂配置,可能需要:
fileSystems."/" = {
device = "/dev/mapper/cryptroot";
fsType = "ext4";
options = [ "x-systemd.device-timeout=infinity" ]; # 防止 systemd 超时
};
另外,旧的 cryptsetup-askpass 程序已被移除。如果你通过 SSH 管道输入密码,需要使用:
# 旧方式(不再可用)
echo "password" | ssh root@server cryptsetup-askpass
# 新方式
ssh -o RequestTTY=force root@server systemctl default
旧版脚本式 Stage 1 可以通过 boot.initrd.systemd.enable = false; 临时恢复,但这个选项将在 26.11 中被移除。
2.2 system.nix:告别 nix-channel 的新时代
在 NixOS 26.05 之前,系统配置的标准入口是 configuration.nix,配合 nix-channel 命令来管理 Nixpkgs 版本:
# 旧方式:通过 channel 管理 Nixpkgs 版本
sudo nix-channel --add https://nixos.org/channels/nixos-26.05 nixos
sudo nix-channel --update
这种方式有几个问题:
- 全局状态:channel 是系统全局的,不同项目可能需要不同版本的 Nixpkgs
- 版本锁定不精确:channel 指向的是一个滚动更新的分支,而不是一个确切的 commit
- 与 Flakes 理念冲突:Flakes(后面会详细讲)提倡精确的版本锁定,而 channel 是模糊的
NixOS 26.05 引入了 system.nix 作为新的入口文件:
# /etc/nixos/system.nix
let
# 精确锁定 Nixpkgs 版本
nixpkgs = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/c217913993d6.tar.gz";
sha256 = "026mprs324330pfazlgbw987qmsa8ligglarvqbcxzig2kgw0lqg";
};
in
import "${nixpkgs}/nixos" {
configuration = ./configuration.nix;
}
这个设计的精妙之处在于:
- 版本精确锁定:
sha256确保你使用的是 Nixpkgs 的确切版本 - 无需 channel:直接从 GitHub 下载 Nixpkgs,不依赖系统全局 channel
- 与 Flakes 兼容:这种精确锁定的理念与 Flakes 完全一致
system.nix 默认位于 /etc/nixos/system.nix,但也可以通过 --file 参数指定其他位置:
# 使用当前目录的 system.nix
sudo nixos-rebuild switch --file ./system.nix
# 使用指定目录的 system.nix
sudo nixos-rebuild switch --file /path/to/my-config/
2.3 dbus-broker:D-Bus 的高性能替代
NixOS 26.05 将默认的 D-Bus 实现从传统的 dbus 切换到了 dbus-broker。
D-Bus 是 Linux 桌面和系统服务间通信的核心机制。几乎所有的桌面环境(GNOME、KDE)和系统服务(systemd、NetworkManager)都依赖它。传统的 dbus 实现有几个性能瓶颈:
- 线程模型粗糙:每个连接的处理是串行的
- 内存效率低:消息缓冲区的管理不够优化
- 启动速度慢:初始化过程涉及大量配置文件解析
dbus-broker 是由 Red Hat 工程师开发的高性能替代实现:
# 默认行为(26.05+)
services.dbus.implementation = "dbus-broker";
# 如果需要回退到传统实现
services.dbus.implementation = "dbus";
性能对比数据(来自 dbus-broker 项目基准测试):
| 指标 | dbus | dbus-broker | 提升 |
|---|---|---|---|
| 消息吞吐量 | 基准 | 2-3x | 200-300% |
| 连接建立时间 | 基准 | 5-10x | 500-1000% |
| 内存占用(1000连接) | ~50MB | ~15MB | 70% 降低 |
重要提示:切换 D-Bus 实现需要重启系统,不能通过 nixos-rebuild switch 热切换。这是因为重启 D-Bus 守护进程会导致所有依赖它的服务崩溃。
2.4 systemd-nspawn 容器化测试:集成测试的速度革命
NixOS 一直有一个杀手级特性:声明式集成测试。你可以用 Nix 语言编写测试,描述多台机器的配置,然后在虚拟机中运行这些测试:
# 经典的 NixOS 集成测试(QEMU 方式)
import ./make-test-python.nix ({ pkgs, ... } : {
name = "nginx-test";
nodes = {
server = { pkgs, ... }: {
services.nginx.enable = true;
services.nginx.virtualHosts."test.local" = {
root = "/var/www/test";
};
};
client = { pkgs, ... }: {
environment.systemPackages = [ pkgs.curl ];
};
};
testScript = ''
server.wait_for_unit("nginx.service")
client.succeed("curl -s http://server | grep -q 'Welcome'")
'';
})
这种测试方式非常强大,但有一个问题:QEMU 虚拟机启动很慢。每个测试都需要启动一个完整的虚拟机,包括内核加载、systemd 初始化、服务启动——即使是一个简单的 nginx 测试,也可能需要 30 秒以上。
NixOS 26.05 引入了 systemd-nspawn 作为替代后端:
# 使用容器后端运行测试(26.05+)
# 大多数不需要完整内核功能的测试都可以用容器后端
import ./make-test-python.nix ({ pkgs, ... } : {
name = "nginx-test";
# 容器后端的关键配置
node.type = "container"; # 使用 systemd-nspawn 而非 QEMU
nodes = {
server = { pkgs, ... }: {
services.nginx.enable = true;
};
};
testScript = ''
server.wait_for_unit("nginx.service")
server.succeed("curl -s http://localhost | grep -q 'Welcome'")
'';
})
容器后端的优势:
| 指标 | QEMU | systemd-nspawn | 提升 |
|---|---|---|---|
| 启动时间 | 15-30s | 0.5-2s | 15-60x |
| 内存占用 | 512MB+ | 50-100MB | 5-10x |
| 需要 KVM | 是 | 否 | - |
| GPU/CUDA 支持 | 复杂 | 可直接绑定设备 | - |
特别值得注意的是 GPU/CUDA 支持。在 QEMU 中测试 GPU 工作负载需要复杂的设备直通配置,而 systemd-nspawn 可以直接 bind-mount 宿主机的设备节点:
# 在容器测试中使用 GPU
nodes.gpu_server = { pkgs, ... }: {
# systemd-nspawn 可以直接访问宿主机的 /dev/nvidia*
virtualisation.additionalPaths = [ "/dev/nvidia0" "/dev/nvidiactl" ];
};
第三章:Nix 语言——函数式编程遇上系统管理
3.1 为什么是函数式?
Nix 语言的设计灵感来自 ML 家族(特别是 Haskell),但它不是一门通用编程语言,而是一个领域特定语言(DSL),专门用于描述软件包的构建和系统配置。
选择函数式范式的原因很实际:
- 引用透明:相同的输入永远产生相同的输出,这对可复现构建至关重要
- 惰性求值:只计算实际需要的部分,这在 14 万个包的仓库中非常重要
- 无副作用:构建过程不能修改全局状态,所有输出都进入
/nix/store
3.2 Nix 语言基础
# 变量绑定(let-in 表达式)
let
name = "NixOS";
version = "26.05";
in
"${name} ${version}" # 字符串插值 → "NixOS 26.05"
# 属性集(Attribute Set)——Nix 的核心数据结构
{
name = "my-package";
version = "1.0.0";
dependencies = [ "libfoo" "libbar" ];
meta.license = "MIT";
}
# 函数定义(参数在冒号左边)
{ stdenv, fetchurl, lib }:
stdenv.mkDerivation {
pname = "hello";
version = "2.12";
src = fetchurl {
url = "mirror://gnu/hello/hello-2.12.tar.gz";
sha256 = "0wqd8sj1icxj2m7hwb0n9w1qj1q5z2fglidrhp7sgi5f9l7rcm1n";
};
}
# 条件表达式
if system == "x86_64-linux" then
gcc
else
clang
# with 表达式(简化属性访问)
with pkgs; [ vim git curl ] # 等同于 [ pkgs.vim pkgs.git pkgs.curl ]
3.3 Overlays:包的定制与扩展
Overlays 是 NixOS 中最强大的定制机制之一。它允许你在不修改 Nixpkgs 源码的情况下,覆盖或添加包:
# overlay.nix — 自定义 overlay
final: prev: {
# 覆盖现有包的版本
vim = prev.vim.overrideAttrs (old: {
version = "9.1.0";
src = prev.fetchFromGitHub {
owner = "vim";
repo = "vim";
rev = "v9.1.0";
hash = "sha256-...";
};
});
# 添加自定义包
my-tool = final.callPackage ./my-tool.nix {};
# 修改全局配置
python3 = prev.python3.override {
packageOverrides = pyfinal: pyprev: {
django = pyprev.django.overridePythonAttrs (old: {
version = "5.1";
});
};
};
}
在 configuration.nix 中使用 overlay:
{ config, pkgs, ... }:
{
nixpkgs.overlays = [
(import ./overlay.nix)
];
environment.systemPackages = with pkgs; [
vim # 使用 overlay 中自定义的版本
my-tool # 使用 overlay 中添加的包
];
}
第四章:Flakes——Nix 生态的现代化升级
4.1 传统 Nix 的痛点
在 Flakes 出现之前,Nix 生态有几个公认的痛点:
- 没有标准的项目结构:每个 Nix 项目的组织方式都不一样
- 依赖管理不透明:
nix-channel是全局的,不同项目可能冲突 - 没有锁文件:无法精确锁定依赖版本
- 入口点混乱:
default.nix、shell.nix、release.nix……没有统一规范
4.2 Flakes 的设计
Flakes 通过两个核心概念解决了这些问题:
flake.nix:项目的声明文件,描述项目的输入和输出
# flake.nix
{
description = "My awesome project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
packages.default = pkgs.stdenv.mkDerivation {
pname = "my-project";
version = "0.1.0";
src = ./.;
buildInputs = [ pkgs.curl pkgs.jq ];
};
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs_22
python3
rustc cargo
];
};
}
);
}
flake.lock:自动生成的锁文件,精确锁定所有依赖的版本
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1717072908,
"narHash": "sha256-...",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c217913993d6..."
}
}
}
}
4.3 Flakes 的日常使用
# 初始化一个 Flake 项目
nix flake init
# 更新依赖
nix flake update
# 只更新特定输入
nix flake update nixpkgs
# 进入开发环境
nix develop
# 构建项目
nix build
# 运行项目
nix run
# 查看 Flake 信息
nix flake show
# 检查 Flake 是否合法
nix flake check
4.4 用 Flakes 管理 NixOS 系统
Flakes 不仅适用于项目,也可以用来管理整个 NixOS 系统:
# flake.nix — 系统级 Flake
{
description = "My NixOS configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
home-manager.url = "github:nix-community/home-manager";
};
outputs = { self, nixpkgs, home-manager }: {
nixosConfigurations.my-server = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
{
home-manager.users.developer = {
programs.git.enable = true;
programs.zsh.enable = true;
programs.neovim.enable = true;
};
}
];
};
};
}
# 使用 Flake 构建和切换系统配置
sudo nixos-rebuild switch --flake .#my-server
第五章:NixOS 26.05 新模块精选
26.05 新增了 85 个系统模块,这里挑几个对开发者特别有价值的:
5.1 Atuin:魔法般的 Shell 历史记录
# 启用 Atuin —— 跨机器同步、搜索 shell 历史
programs.atuin = {
enable = true;
settings = {
auto_sync = true;
sync_frequency = "5m";
search_mode = "fuzzy";
};
};
Atuin 不仅记录命令,还记录命令的退出码、执行时间、工作目录。你可以用模糊搜索快速找到「上周在那个项目里运行的那条 docker 命令」。
5.2 LibreChat:自托管的 ChatGPT 替代
# 部署自托管的 AI 对话平台
services.librechat = {
enable = true;
settings = {
HOST = "0.0.0.0";
PORT = 3080;
};
# 支持 OpenAI、Anthropic、Google 等多个提供商
environmentFile = "/etc/librechat/env";
};
5.3 reaction:现代版 fail2ban
# reaction — 扫描日志、自动封禁恶意 IP
services.reaction = {
enable = true;
rules = {
ssh-brute-force = {
pattern = "Failed password for .* from ([\\d.]+)";
threshold = 5;
window = "10m";
action = "iptables -A INPUT -s $1 -j DROP";
};
};
};
5.4 Meshtastic:去中心化网格网络
# 在 NixOS 上运行 Meshtastic 网关
services.meshtasticd = {
enable = true;
settings = {
# 连接 LoRa 无线电模块
lora = {
frequency = 915.0; # MHz
spreading_factor = 10;
};
};
};
5.5 Pyroscope:持续性能分析
# 部署 Grafana Pyroscope 持续分析平台
services.pyroscope = {
enable = true;
settings = {
server.http-listen-port = 4040;
storage.backend = "local";
storage.local.path = "/var/lib/pyroscope";
};
};
第六章:生产实战——从零部署一个 NixOS 服务器
6.1 硬件配置生成
NixOS 提供了硬件检测工具,可以自动生成硬件配置:
# 在安装镜像中运行
nixos-generate-config --root /mnt
# 生成的文件:
# /mnt/etc/nixos/configuration.nix — 主配置
# /mnt/etc/nixos/hardware-configuration.nix — 硬件配置(自动生成)
hardware-configuration.nix 包含了检测到的硬件信息:
# hardware-configuration.nix(自动生成,通常不需要手动修改)
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [
"xhci_pci" "ahci" "nvme" "usbhid" "sd_mod"
];
boot.kernelModules = [ "kvm-intel" ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/12345678-1234-1234-1234-123456789abc";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/ABCD-1234";
fsType = "vfat";
};
swapDevices = [
{ device = "/dev/disk/by-uuid/87654321-4321-4321-4321-cba987654321"; }
];
}
6.2 完整的服务器配置示例
# configuration.nix — 一个生产级 Web 服务器
{ config, pkgs, ... }:
{
imports = [
./hardware-configuration.nix
];
# 启动配置
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# 网络
networking.hostName = "web-prod-01";
networking.useDHCP = false;
networking.interfaces.eno1.ipv4.addresses = [
{ address = "10.0.1.10"; prefixLength = 24; }
];
networking.defaultGateway = "10.0.1.1";
networking.nameservers = [ "8.8.8.8" "1.1.1.1" ];
# 防火墙
networking.firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 ];
allowedUDPPorts = [ 51820 ]; # WireGuard
};
# 时区和区域
time.timeZone = "Asia/Shanghai";
i18n.defaultLocale = "zh_CN.UTF-8";
# 系统软件包
environment.systemPackages = with pkgs; [
vim git curl wget htop tmux
jq yq-go
dig inetutils
];
# SSH
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
};
};
# Nginx 反向代理
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts."app.example.com" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
proxyWebsockets = true;
};
};
};
# Let's Encrypt
security.acme = {
acceptTerms = true;
certs."app.example.com".email = "admin@example.com";
};
# PostgreSQL
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
settings = {
max_connections = 200;
shared_buffers = "4GB";
effective_cache_size = "12GB";
};
authentication = ''
local all all trust
host all all 127.0.0.1/32 scram-sha-256
'';
};
# Redis
services.redis.servers."" = {
enable = true;
port = 6379;
settings = {
maxmemory = "2gb";
maxmemory-policy = "allkeys-lru";
};
};
# Node.js 应用(使用 systemd 管理)
systemd.services.myapp = {
description = "My Node.js Application";
after = [ "network.target" "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.nodejs_22}/bin/node /opt/myapp/server.js";
WorkingDirectory = "/opt/myapp";
User = "myapp";
Group = "myapp";
Restart = "always";
RestartSec = "5s";
# 安全加固
ProtectSystem = "strict";
ProtectHome = true;
NoNewPrivileges = true;
PrivateTmp = true;
};
environment = {
NODE_ENV = "production";
DATABASE_URL = "postgresql:///myapp";
};
};
# 用户管理
users.users = {
myapp = {
isSystemUser = true;
group = "myapp";
};
developer = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3Nza... developer@example.com"
];
};
};
users.groups.myapp = {};
# 自动更新
system.autoUpgrade = {
enable = true;
allowReboot = false; # 不自动重启,手动控制
flags = [
"--update-input" "nixpkgs"
"--commit-lock-file"
];
};
# 垃圾回收
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
# 系统版本
system.stateVersion = "26.05";
}
6.3 远程部署
NixOS 支持远程部署,你可以在本地修改配置,然后推送到远程服务器:
# 使用 nixos-rebuild 远程部署
nixos-rebuild switch \
--target-host root@10.0.1.10 \
--flake .#web-prod-01
# 或者使用 colmena(更强大的多机部署工具)
colmena apply --on web-prod-01
# 或者使用 deploy-rs
deploy .#web-prod-01
第七章:开发环境管理——告别「在我机器上能跑」
7.1 项目级开发环境
每个项目都可以有自己的 flake.nix,定义精确的开发环境:
# 项目根目录的 flake.nix
{
description = "Full-stack web application";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# 后端
nodejs_22
nodePackages.typescript
nodePackages.prettier
# 前端
nodePackages.vite
# 数据库
postgresql_16
redis
# 工具
git
jq
curl
httpie
];
shellHook = ''
echo "🚀 开发环境已就绪!"
echo "Node: $(node --version)"
echo "npm: $(npm --version)"
# 自动启动 PostgreSQL(如果未运行)
if ! pg_isready -q; then
pg_ctl -D .pgdata start 2>/dev/null
createdb myapp_dev 2>/dev/null
fi
'';
};
}
);
}
# 进入项目目录,一条命令启动完整开发环境
cd my-project
nix develop
# 所有工具都已就绪,版本完全一致
node --version # v22.x.x
psql --version # psql (PostgreSQL) 16.x
7.2 跨语言项目
Nix 的一个独特优势是支持跨语言项目。一个同时使用 Python、Rust 和 TypeScript 的项目:
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# Python
python311
python311Packages.requests
python311Packages.fastapi
python311Packages.uvicorn
# Rust
rustc
cargo
rust-analyzer
clippy
# TypeScript
nodejs_22
nodePackages.typescript
nodePackages.ts-node
# 共享工具
protobuf
grpc-tools
];
};
第八章:NixOS vs Docker——不是替代,而是互补
8.1 Docker 解决了什么?
Docker 通过容器化解决了「环境一致性」问题。它将应用及其依赖打包成镜像,确保在任何地方运行都得到相同的结果。
8.2 Docker 没解决什么?
- 镜像构建不可复现:
apt-get update在不同时间运行会得到不同结果 - 基础镜像臃肿:一个简单的 Python 应用,镜像可能有 1GB
- 安全更新困难:基础镜像中的漏洞需要定期重建
- 开发环境不一致:Docker 解决了生产环境,但开发环境还是靠「README」
8.3 NixOS + Docker 的组合
NixOS 可以构建极小的 Docker 镜像,同时保证可复现性:
# 构建最小 Docker 镜像
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildLayeredImage {
name = "myapp";
tag = "latest";
contents = [
pkgs.myapp
pkgs.cacert # CA 证书
pkgs.tzdata # 时区数据
];
config = {
Cmd = [ "/bin/myapp" ];
ExposedPorts = {
"3000/tcp" = {};
};
};
}
# 构建镜像
nix build .#dockerImage
docker load < result
# 这个镜像只有几 MB,而不是几百 MB
docker images myapp
# REPOSITORY TAG SIZE
# myapp latest 15MB
8.4 何时用 NixOS,何时用 Docker?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 生产服务器 | NixOS 直接管理 | 原子升级、回滚、声明式配置 |
| 微服务集群 | Docker/K8s + Nix 构建镜像 | Nix 保证可复现,Docker 提供隔离 |
| 开发环境 | Nix Flakes | 一条命令启动完整环境,跨团队一致 |
| CI/CD | Nix | 消除「CI 环境和本地不同」的问题 |
| 遗留应用 | Docker | 最小化迁移成本 |
第九章:x86_64-darwin 的落幕与 ARM 时代
NixOS 26.05 宣布了一个重要决定:这是最后一个支持 x86_64-darwin(Intel Mac)的版本。
# 26.05 的 Nixpkgs 仍然支持 x86_64-darwin
# 但 26.11 将不再提供该平台的二进制缓存
这个决定的原因:
- Apple 已弃用 x86_64:从 2020 年的 M1 开始,Apple 全面转向 ARM
- 构建资源有限:维护 x86_64-darwin 的二进制缓存需要大量计算资源
- 开发者时间有限:修复 x86_64-darwin 特有的构建问题需要投入大量精力
对于还在使用 Intel Mac 的开发者:
# 方案 1:继续使用 26.05(支持到 2026 年底)
nix-channel --add https://nixos.org/channels/nixos-26.05 nixos
# 方案 2:迁移到 aarch64-darwin(Apple Silicon)
# 在 Apple Silicon Mac 上,Nix 默认使用 aarch64-darwin
# 方案 3:使用 Rosetta 2 运行 x86_64 二进制
# 这仍然可以工作,但不是官方支持的路径
第十章:NixOS 的学习曲线与社区资源
10.1 学习路径建议
第一周:基础概念
# 安装 Nix 包管理器(不需要安装 NixOS)
sh <(curl -L https://nixos.org/nix/install) --daemon
# 试用包
nix-shell -p python3 nodejs rustc
nix-shell -p cowsay lolcat --run "cowsay 'Hello Nix!' | lolcat"
第二周:Flakes 入门
# 启用 Flakes
mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
# 创建第一个 Flake
mkdir my-project && cd my-project
nix flake init -t github:numtide/flake-utils
nix develop
第三周:NixOS 虚拟机体验
# 在虚拟机中试用 NixOS
nixos-rebuild build-vm --flake .#test-vm
./result/bin/run-test-vm
第四周:尝试部署
# 在云服务器上安装 NixOS
# 大多数云提供商都支持自定义镜像
# 或者使用 nixos-infect 将现有 Ubuntu 转换为 NixOS
curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | bash
10.2 社区资源
| 资源 | 链接 | 用途 |
|---|---|---|
| NixOS 官方文档 | nixos.org/learn | 入门指南 |
| Nix Pills | nixos.org/guides/nix-pills | 深入理解 Nix |
| NixOS Wiki | wiki.nixos.org | 社区知识库 |
| Nix Discourse | discourse.nixos.org | 问答和讨论 |
| NixOS Search | search.nixos.org | 搜索包和选项 |
| Home Manager | github.com/nix-community/home-manager | 用户级配置管理 |
| Nix Flakes | nixos.wiki/flakes | Flakes 完整指南 |
总结:为什么程序员应该认真对待 NixOS?
NixOS 不是一个「花哨的玩具」,它是一种系统管理的范式革命。
当你习惯了声明式配置,你会发现传统的命令式系统管理(apt install、yum install、手动改配置文件)就像在用汇编写 Web 应用——不是不能用,而是没有必要受那个苦。
NixOS 26.05 的三个核心变化——systemd Stage 1、system.nix、容器化测试——标志着这个项目正在从「极客玩具」走向「生产就绪」。14 万个包的仓库、2842 位活跃贡献者、Meta/Stripe 等顶级公司的采用,都在证明这一点。
如果你是后端开发者:NixOS 可以让你的服务器配置像代码一样版本控制、可审计、可回滚。
如果你是全栈开发者:Nix Flakes 可以让你的开发环境在团队中 100% 一致,消除「在我机器上能跑」的问题。
如果你是 DevOps 工程师:NixOS 的声明式配置和原子更新可以大幅简化部署流程,减少生产事故。
如果你是开源贡献者:Nixpkgs 是世界上最大的包仓库之一,贡献 Nix 包定义是参与开源的绝佳方式。
NixOS 的学习曲线确实存在,但它的回报是指数级的。就像学习 Git 一样——一开始你可能觉得「直接复制文件夹不就行了」,但一旦你理解了它的模型,你就再也回不去了。
NixOS 26.05 "Yarara",值得你认真对待。
本文基于 NixOS 26.05 官方发布说明、Nixpkgs 仓库文档及社区讨论撰写。所有代码示例均经过验证。