编程 NixOS 26.05 深度解析:声明式系统的终极形态——从 systemd Stage 1 到 14 万包仓库,程序员为什么要认真对待这个「异类」发行版

2026-07-06 00:43:30 +0800 CST views 30

NixOS 26.05 深度解析:声明式系统的终极形态——从 systemd Stage 1 到 14 万包仓库,程序员为什么要认真对待这个「异类」发行版

前言:一个让 Docker 都感到不安的发行版

2026 年 5 月 30 日,NixOS 项目发布了代号为 "Yarara" 的 26.05 版本。这个版本带来了 2842 位贡献者的 59703 次提交,新增 20442 个软件包,更新 20641 个,移除 17532 个过时包。

这些数字本身已经足够震撼,但真正让这个版本成为里程碑的,是三个足以改变 Linux 系统管理范式的变化:

  1. systemd Stage 1 成为默认启动方式——彻底告别脚本式 initrd
  2. system.nix 全新入口文件——声明式配置不再依赖 nix-channel
  3. 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 会:

  1. 解析配置文件,计算出需要哪些包和服务
  2. 只构建和部署发生变化的部分
  3. 创建一个新的系统「世代(generation)」
  4. 原子性地切换到新世代

如果新配置有问题,你可以随时回滚到上一个世代——不需要重装系统,不需要手动撤销修改,一条命令搞定:

# 回滚到上一个世代
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 带来的好处:

  1. 原生 LUKS2 支持:systemd-cryptsetup 提供更可靠的加密卷处理
  2. 并行设备发现:systemd-udev 的事件驱动模型替代了顺序扫描
  3. 统一的日志系统:启动过程中的所有日志都通过 journald 收集,可以用 journalctl -b 查看完整的 initrd 日志
  4. 更好的错误处理: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

这种方式有几个问题:

  1. 全局状态:channel 是系统全局的,不同项目可能需要不同版本的 Nixpkgs
  2. 版本锁定不精确:channel 指向的是一个滚动更新的分支,而不是一个确切的 commit
  3. 与 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 项目基准测试):

指标dbusdbus-broker提升
消息吞吐量基准2-3x200-300%
连接建立时间基准5-10x500-1000%
内存占用(1000连接)~50MB~15MB70% 降低

重要提示:切换 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'")
  '';
})

容器后端的优势:

指标QEMUsystemd-nspawn提升
启动时间15-30s0.5-2s15-60x
内存占用512MB+50-100MB5-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),专门用于描述软件包的构建和系统配置。

选择函数式范式的原因很实际:

  1. 引用透明:相同的输入永远产生相同的输出,这对可复现构建至关重要
  2. 惰性求值:只计算实际需要的部分,这在 14 万个包的仓库中非常重要
  3. 无副作用:构建过程不能修改全局状态,所有输出都进入 /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 生态有几个公认的痛点:

  1. 没有标准的项目结构:每个 Nix 项目的组织方式都不一样
  2. 依赖管理不透明nix-channel 是全局的,不同项目可能冲突
  3. 没有锁文件:无法精确锁定依赖版本
  4. 入口点混乱default.nixshell.nixrelease.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 没解决什么?

  1. 镜像构建不可复现apt-get update 在不同时间运行会得到不同结果
  2. 基础镜像臃肿:一个简单的 Python 应用,镜像可能有 1GB
  3. 安全更新困难:基础镜像中的漏洞需要定期重建
  4. 开发环境不一致: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/CDNix消除「CI 环境和本地不同」的问题
遗留应用Docker最小化迁移成本

第九章:x86_64-darwin 的落幕与 ARM 时代

NixOS 26.05 宣布了一个重要决定:这是最后一个支持 x86_64-darwin(Intel Mac)的版本

# 26.05 的 Nixpkgs 仍然支持 x86_64-darwin
# 但 26.11 将不再提供该平台的二进制缓存

这个决定的原因:

  1. Apple 已弃用 x86_64:从 2020 年的 M1 开始,Apple 全面转向 ARM
  2. 构建资源有限:维护 x86_64-darwin 的二进制缓存需要大量计算资源
  3. 开发者时间有限:修复 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 Pillsnixos.org/guides/nix-pills深入理解 Nix
NixOS Wikiwiki.nixos.org社区知识库
Nix Discoursediscourse.nixos.org问答和讨论
NixOS Searchsearch.nixos.org搜索包和选项
Home Managergithub.com/nix-community/home-manager用户级配置管理
Nix Flakesnixos.wiki/flakesFlakes 完整指南

总结:为什么程序员应该认真对待 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 仓库文档及社区讨论撰写。所有代码示例均经过验证。

推荐文章

Vue3中如何处理路由和导航?
2024-11-18 16:56:14 +0800 CST
Vue中的`key`属性有什么作用?
2024-11-17 11:49:45 +0800 CST
在 Rust 生产项目中存储数据
2024-11-19 02:35:11 +0800 CST
Nginx rewrite 的用法
2024-11-18 22:59:02 +0800 CST
PHP解决XSS攻击
2024-11-19 02:17:37 +0800 CST
7种Go语言生成唯一ID的实用方法
2024-11-19 05:22:50 +0800 CST
程序员出海搞钱工具库
2024-11-18 22:16:19 +0800 CST
在Vue3中实现代码分割和懒加载
2024-11-17 06:18:00 +0800 CST
乐观锁和悲观锁,如何区分?
2024-11-19 09:36:53 +0800 CST
程序员茄子在线接单