NixOS 深度拆解:当声明式配置成为操作系统的第一性原理——从 flake 到原子化升级的工程全貌
2026年,NixOS 正在从极客小众走向主流工程实践。Determinate Nix Installer 下载量突破 700 万次,NixOS 中文社区活跃度同比增长 340%。这不是偶然——当容器化、不可变基础设施和 GitOps 理念深入人心,NixOS 的声明式配置哲学恰好踩中了云原生时代工程师的集体焦虑:如何让开发环境可复现、如何让系统状态可回滚、如何让团队协作不再因为「在我机器上能跑」而崩溃?本文从工程师视角,深度拆解 Nix/NixOS 的架构设计、Nix 语言的核心机制、flakes 的来龙去脉,以及在 2026 年如何用 NixOS 重构你的开发工作流。
一、背景:从配置地狱到声明式乌托邦
1.1 每个工程师都经历过的噩梦
想象这样一个场景:你在本地开发环境折腾了三天,终于把所有依赖版本调通——Python 3.11.4、Node.js 18.17.0、PostgreSQL 14.5、Redis 7.0.4,连 CUDA 版本都是 12.2。代码在本地跑得飞起。然后你把项目交给同事,同事的机器报了一屏幕的依赖冲突。你开始写 README:「首先安装 Python 3.11... 等等,不对,是 3.11.4,不是 3.11.3...」
这不是一个工程问题,这是一个文化问题。传统 Linux 系统的配置是命令式的:一步一步执行安装命令,系统状态是这些命令累积执行的结果。但命令的执行顺序、环境差异、版本漂移都会导致最终状态的不一致。我们试图用 Docker、Ansible、Homebrew 的各种方案来缓解,但本质上都是在打补丁。
NixOS 的核心主张是:把操作系统当作代码来写。 你的 configuration.nix 文件就是系统的完整规格说明,用 Nix 语言描述你想要什么样的系统,然后 Nix 直接从规格推导出系统的最终状态。无论你在这台机器上构建多少次,结果都是一致的。
1.2 NixOS 是什么
NixOS 是基于 Nix 包管理器的 Linux 发行版,由 Eelco Dolstra 于 2003 年发起,至今已发展超过 20 年。与传统发行版的根本区别:
| 维度 | 传统发行版(Ubuntu/CentOS) | NixOS |
|---|---|---|
| 包管理 | 安装到固定路径(/usr/bin) | 安装到 Nix Store(哈希路径) |
| 配置方式 | 命令式(apt install / yum install) | 声明式(描述期望状态) |
| 多版本共存 | 需要手动版本切换(pyenv/rvm) | 原生支持,同时存在 |
| 升级回滚 | 升级覆盖,回滚困难 | 原子化切换,随时回滚 |
| 可复现性 | 依赖外部环境 | 配置即规格,规格即系统 |
1.3 2026 年的生态现状
截至 2026 年 7 月,Nix 生态有几个值得关注的里程碑:
- Determinate Nix Installer 下载量突破 700 万次,解决了 Nix 官方安装器长期以来的体验问题(非单用户模式下需要手动配置 service)
- flakes 成为事实标准:Nix 2.18 正式稳定了 flakes 特性,几乎所有新项目都默认使用 flake.nix 管理依赖
- ** devenv.sh 集成**:由 cachix 团队维护的 devenv.sh 已成为 NixOS/nix-darwin 上最流行的开发环境管理工具
- home-manager 成熟:用户配置管理从实验性功能升级为稳定生态,支持跨机器同步 dotfiles
二、核心概念:Nix 语言的第一性原理
2.1 函数式语言的核心设计
Nix 语言是一门纯函数式、延迟求值(lazy evaluation)的领域特定语言(DSL),编译为 C 代码执行。它的设计目标只有一个:描述「构建过程」,并保证构建结果可复现。
让我们从最基础的表达式开始理解 Nix 语言的设计哲学:
# Nix 语言的几个核心概念
# 1. 一切皆表达式,没有语句
# 传统 Shell: if [ $x -eq 1 ]; then echo "yes"; fi
# Nix:
let
x = 1;
in
if x == 1 then "yes" else "no"
# 2. 属性集(Attribute Set)- 类似 JSON 的数据结构
myConfig = {
name = "my-project";
version = "1.0.0";
dependencies = [ "python" "postgresql" ];
settings = {
port = 8080;
debug = true;
};
};
# 访问属性
myConfig.name # => "my-project"
myConfig.settings.port # => 8080
# 3. 路径类型 - Nix 能理解路径的哈希
# Nix 会自动计算路径内容的哈希,用于内容寻址
# /nix/store/abc123-hello -> 内容 abc123 是文件内容的哈希
为什么 Nix 语言要这样设计? 关键在于引用透明性(Referential Transparency):一个 Nix 表达式,给定相同的输入,总是产生相同的输出。这让我们能够精确地追踪「这个包依赖哪些文件」和「这个系统由哪些包组成」。
2.2 Nix Store:内容寻址的包存储
Nix Store 是整个系统最核心的设计,也是理解 Nix 可复现性的关键。
在传统 Linux 系统中:
/usr/bin/python -> /usr/bin/python3.11
包管理器覆盖式安装,每次升级都会替换文件。如果新版本有 bug,你只能祈祷有备份。
在 Nix 中:
/nix/store/abc1234-python3.11.4/lib/python3.11/site-packages/pkg/
/nix/store/def5678-python3.11.5/lib/python3.11/site-packages/pkg/
每个包都被安装到一个由内容哈希命名的目录中。哈希是基于包的内容计算出来的——这意味着:
- 相同内容的包,路径永远相同
- 不同版本的包,路径必然不同
- 旧版本不会被覆盖,只是不再被引用
这个设计叫做 内容寻址存储(Content-Addressable Store)。类比一下:就像 Git 的 commit hash——相同内容的提交,hash 永远一样;不同内容必然不同 hash。
2.3 Derivation:构建过程的形式化描述
Derivation(推导)是 Nix 中「构建」的概念——它用 Nix 语言描述了一个构建任务的完整规格:
# 这是一个 derivation 的简化示例
# 实际使用时通过 mkDerivation 构造
derivation {
system = "x86_64-linux";
builder = "/bin/bash";
args = [ "-c" "echo 'Building...' && gcc hello.c -o hello" ];
# gcc 这个包,作为输入,也会被哈希进输出路径
# 所以如果 gcc 版本变了,hello 的路径也会变
gcc = (import <nixpkgs> {}).gcc;
# 原始输入文件
src = ./hello.c;
}
执行这个 derivation 的结果是什么?Nix 会:
- 计算所有输入的哈希(包括 gcc 的路径、
hello.c的内容) - 根据输入哈希计算出输出路径:
/nix/store/<hash>-hello - 在隔离的 sandbox 环境中执行构建
- 将构建产物放入该路径
这意味着:如果你在两台机器上用相同的 nixpkgs revision 和相同的源码构建,得到的结果路径完全相同。 这就是可复现性的数学基础。
2.4 延迟求值与 DAG 依赖
Nix 语言的 lazy evaluation 特性让构建过程变成一个有向无环图(DAG)。当你写:
let
pkgs = import <nixpkgs> {};
python = pkgs.python311;
django = pkgs.callPackage ./django-overlay.nix { inherit python; };
myapp = pkgs.callPackage ./myapp.nix { inherit django; };
in
myapp
Nix 不会立即计算所有包——它只在你真正需要时才求值。但同时,它会构建完整的依赖图,并自动检测循环依赖。这带来的实际好处是:Nix 可以高效地分析依赖关系,告诉你「如果我升级这个包,哪些包会受影响」。
三、声明式系统配置:从 configuration.nix 到完整规格
3.1 配置文件结构
NixOS 的核心是 /etc/nixos/configuration.nix。这是一个完整的 Nix 表达式,返回一个 NixOS 模块系统(Module System)配置:
# /etc/nixos/configuration.nix
{ config, pkgs, ... }:
{
imports = [
# 导入其他模块(如硬件配置)
./hardware-configuration.nix
# 导入 home-manager 模块
<home-manager/nixos>
];
# =====================
# 网络引导启动 (Bootloader)
# =====================
boot.loader.grub = {
enable = true;
device = "/dev/sda";
configurationLimit = 10; # 保留最近 10 个启动项
};
# =====================
# 文件系统配置
# =====================
fileSystems."/" = {
device = "/dev/sda1";
fsType = "ext4";
};
# =====================
# 用户管理
# =====================
users.users.qnnet = {
isNormalUser = true;
description = "Developer";
extraGroups = [ "wheel" "docker" "networkmanager" ];
# 这里可以直接写 shell = pkgs.fish;
# 而不是通过命令安装 fish
};
# =====================
# 系统包(所有用户共享)
# =====================
environment.systemPackages = with pkgs; [
# git 和传统包管理器安装没什么区别
git
curl
wget
# 但这里可以指定精确版本!
# 不再受制于发行版的版本库
python311
nodejs_18
postgresql_15
# Docker 和 Docker Compose
docker-compose
# 甚至可以安装 GUI 应用
firefox
vscode
];
# =====================
# 系统服务(systemd)
# =====================
services.postgresql = {
enable = true;
package = pkgs.postgresql_15;
# 直接在配置里写 postgresql.conf 参数
settings = {
max_connections = 200;
shared_buffers = "256MB";
effective_cache_size = "1GB";
};
# 也可以写 SQL 初始化脚本
initialScript = pkgs.writeText "init.sql" ''
CREATE DATABASE myapp;
CREATE USER myapp WITH PASSWORD 'secret';
'';
};
# =====================
# Docker 服务
# =====================
virtualisation.docker = {
enable = true;
autoPrune = {
enable = true;
dates = "weekly";
flags = [ "--all" ];
};
};
# =====================
# 时区和语言
# =====================
time.timeZone = "Asia/Shanghai";
i18n.defaultLocale = "zh_CN.UTF-8";
i18n.supportedLocales = [ "zh_CN.UTF-8" "en_US.UTF-8" ];
# =====================
# 内核模块(自动加载哪些内核模块)
# =====================
boot.kernelModules = [ "zfs" "nvidia" ];
boot.extraModulePackages = with pkgs; [ nvidia_x11 ];
# =====================
# 网络配置
# =====================
networking.hostName = "nixos-workstation";
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
# =====================
# 安全性
# =====================
security.sudo.wheelNeedsPassword = false;
programs.zsh.enable = true;
# 应用到系统
system.stateVersion = "24.05";
}
这段配置翻译成传统命令式操作,大约相当于执行了:
# 这只是一个粗略的等价命令列表
# 实际操作远比这复杂
apt update && apt install -y git curl wget docker.io postgresql-15
systemctl enable postgresql
systemctl start postgresql
# ... 几十到上百条命令
关键区别:命令式操作需要你知道「做什么」,声明式配置只需要你描述「要什么」。系统状态的推导由 NixOS 完成。
3.2 NixOS Module System:配置的组合数学
NixOS 真正强大的地方在于模块系统(Module System)。NixOS 的配置不是一个大文件,而是一组可以自由组合的模块。每个模块都可以定义一些选项(Option),其他模块可以引用这些选项:
# my-nginx-module.nix
# 这是一个自定义模块,定义了一个 nginx 网站配置选项
{ config, lib, pkgs, ... }:
{
options = {
my-nginx-sites = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
serverName = lib.mkOption { type = lib.types.str; };
root = lib.mkOption { type = lib.types.str; };
enableSSL = lib.mkOption { type = lib.types.bool; default = false; };
};
});
default = {};
};
};
config = {
services.nginx = {
virtualHosts = lib.mapAttrs (name: site: {
inherit (site) serverName root;
enableACME = site.enableSSL;
forceSSL = site.enableSSL;
}) config.my-nginx-sites;
};
};
}
然后在 configuration.nix 中使用:
imports = [ ./my-nginx-module.nix ];
my-nginx-sites.my-blog = {
serverName = "blog.example.com";
root = "/var/www/blog";
enableSSL = true;
};
这种设计的优雅之处在于:你可以在网上找到任意 NixOS 模块,直接复制到项目中,就能以声明式的方式引入新的系统能力。就像前端工程师引入一个 npm 包一样简单。
四、Flakes:从实验性特性到生态系统的事实标准
4.1 Flakes 解决了什么问题
Nix 传统的 <nixpkgs> 引用方式存在一个根本性问题:它无法精确锁定依赖版本。
# 传统方式 - 问题:master 分支随时变
nix-env -f '<nixpkgs>' -iA python311
# flake 方式 - 精确锁定
# flake.nix 中声明:
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
# 每次 nixos-rebuild 都会使用完全相同的 nixpkgs revision
}
Flakes 是 Nix 2.0 引入的实验性特性,经过多年迭代在 Nix 2.18 正式稳定。它的核心是引入了一个标准化的项目描述文件 flake.nix 和一个全局的 flake 注册表。
4.2 flake.nix 的结构
# flake.nix
{
description = "My Development Environment";
# ========== 输入声明 ==========
inputs = {
# Nixpkgs - 锁定版本
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
# home-manager - 用户配置管理
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs"; # 和主 nixpkgs 同步
};
# devenv - 开发环境
devenv = {
url = "github:cachix/devenv";
inputs.nixpkgs.follows = "nixpkgs";
};
# 你的自定义包
my-overlay = {
url = "path:./overlays/my-pkg";
inputs.nixpkgs.follows = "nixpkgs";
};
};
# ========== 输出定义 ==========
outputs = { self, nixpkgs, home-manager, devenv, my-overlay, ... }@inputs:
let
# 为每个系统平台构建
forAllSystems = nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
in
{
# NixOS 配置输出
nixosConfigurations = {
my-laptop = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; }; # 传递 flake inputs 给 modules
modules = [
./hosts/laptop/configuration.nix
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.qnnet = import ./home/qnnet.nix;
home-manager.activationPackage = (self: hm: hm.path);
}
];
};
my-server = nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
specialArgs = { inherit inputs; };
modules = [ ./hosts/server/configuration.nix ];
};
};
# 开发环境(devenv)
devShells = forAllSystems (system:
let pkgs = nixpkgs.legacyPackages.${system};
in {
default = devenv.lib.mkDevShell {
inherit inputs pkgs;
modules = [ ./devenv.nix ];
};
}
);
# overlay - 将自定义包注入 nixpkgs
overlay = final: prev: {
my-package = my-overlay.packages.${system}.default;
};
# 打包输出(可供 nix build 使用)
packages = forAllSystems (system:
let pkgs = nixpkgs.legacyPackages.${system};
in {
default = pkgs.my-package;
my-app = pkgs.callPackage ./my-app {};
}
);
};
}
这个文件结构中蕴含了 flake 设计的几个核心思想:
- 输入锁定(Input Locking):flake.lock 文件记录了所有输入的精确哈希——这意味着每次 clone 项目并构建,结果都完全一致
- 跨平台输出:同一个 flake.nix 可以为 Linux 和 macOS(通过 nix-darwin)生成配置
- 组合性:overlay 系统让你可以在不 fork nixpkgs 的情况下添加自己的包
4.3 flake.lock:精确依赖锁定的实现
执行 nix flake update 后生成的 flake.lock 文件:
{
"version": 7,
"inputs": {
"nixpkgs": {
"type": "github",
"owner": "NixOS",
"repo": "nixpkgs",
"ref": "nixos-24.05",
"rev": "7c9cc5a6e5d38082001718a46bb0e8f2c7a5f2b4",
"lastModified": 1719878400
},
"home-manager": {
"type": "github",
"owner": "nix-community",
"repo": "home-manager",
"ref": "release-24.05",
"rev": "a1b2c3d4e5f6789012345678abcdef01",
"lastModified": 1719500000
}
}
}
这个文件就是 Flakes 可复现性的关键。它记录了每个输入的精确 Git commit SHA。和 Go 的 go.sum、Node 的 package-lock.json、Python 的 Poetry.lock 是同一个思想,只是 Nix 把锁定的粒度扩展到了整个系统和所有依赖。
4.4 devenv.sh:开发环境的原子化
对于日常开发,最常用的不是 nixos-rebuild,而是 devenv.sh——一个基于 Nix 的开发环境工具。它让你为每个项目定义一个 devenv.nix:
# devenv.nix
{ pkgs, ... }:
{
# Python 环境
languages.python = {
enable = true;
version = "3.11";
packages = pypi-deps.generatePackageList {
pythonVersion = "3.11";
requirements = builtins.readFile ./requirements.txt;
};
};
# Node.js 环境
languages.nodejs = {
enable = true;
version = "20";
packageLock = ./package-lock.json;
};
# 数据库服务
services.postgres = {
enable = true;
port = 5432;
initialScript = ./init.sql;
settings = {
max_connections = 100;
shared_buffers = "128MB";
};
};
# Redis
services.redis.enable = true;
# 环境变量
env.GIN_MODE = "release";
env.DATABASE_URL = "postgres://localhost:5432/myapp";
# 启动脚本
enterShell = ''
echo "Welcome to the dev environment!"
# 可以在这里跑任意初始化命令
alembic upgrade head
'';
}
开发者只需要在项目根目录运行 devenv up,就能启动一个完整的、可复现的开发环境:Python 包、Node 包、PostgreSQL、Redis 全部自动配置好,而且和 CI 环境完全一致。
五、原子化升级与回滚:系统状态的版本控制
5.1 系统配置的版本化
NixOS 最有实用价值的功能之一:每次系统配置变更都会生成一个新的启动项(generation),可以随时回滚。
# 查看所有系统代数(generations)
nix-env --list-generations
# 输出类似:
# 97 2026-07-10 14:30:12
# 98 2026-07-12 09:15:33 ← 当前
# 99 2026-07-14 11:00:01
# 100 2026-07-16 08:45:22 ← 刚刚构建
# 回滚到上一个代数
sudo /run/current-system/sw/bin/switch-to-configuration boot
# 或者直接回滚
sudo nixos-rebuild switch --rollback
# 升级系统
sudo nixos-rebuild switch --upgrade
# 构建但不激活(先预览)
sudo nixos-rebuild build --no-out-link
这个机制背后的原理:NixOS 的 /run/current-system 是一个符号链接,指向当前活跃的系统配置。当执行 nixos-rebuild switch 时:
- Nix 根据
configuration.nix构建新系统(产生新的 store path) - 将符号链接指向新的 store path
- 旧系统配置仍然存在于 store 中,只是没有符号链接指向它
所以即使激活了新系统,旧系统依然可以通过选择旧的启动项恢复。
5.2 软件包级别的回滚
不仅是系统级别,单个软件包也可以回滚:
# 列出某个包的多个版本
nix-env --list-generations --profile /nix/var/nix/profiles/system
# 查看某个包的构建历史
nix log python311 # 查看 Python 的构建过程
# 回滚到旧版本(通过 generational rollback)
nix-env --rollback
# 或者显式安装某个旧版本
nix-env --install python311.10 # 安装到 python311.10
5.3 NixOS 中的 GitOps 实践
结合 Git,NixOS 可以实现完整的 GitOps:
# 典型的工作流
cd ~/nixos-config
git pull # 拉取最新配置
nixos-rebuild switch --flake . # 从 flake 构建并激活
# 如果出问题
git log --oneline -5 # 看看改了什么
git diff HEAD~1 # 对比差异
git checkout HEAD~1 # 回到上一个版本
nixos-rebuild switch --rollback # 回滚系统
对于团队协作,可以把 NixOS 配置放在私有 Git 仓库中:
# 从私有 Git 仓库拉取配置
inputs.my-config.url = "git+ssh://git@github.com/myorg/nixos-config";
inputs.my-config.inputs.nixpkgs.follows = "nixpkgs";
六、home-manager:用户配置的可复现化管理
6.1 dotfiles 的传统困境
经典的 dotfiles 管理难题:.bashrc、.vimrc、.gitconfig、.ssh/config 等散落在 $HOME 目录的各个角落。你要同步到新机器,要么用 symlink 方案(GNU Stow),要么用专门的 dotfiles 管理器。
home-manager 把这个问题的解决方案提升到了声明式的级别:
# home/qnnet.nix
{ config, pkgs, ... }:
{
# home-manager 有一个等效于 configuration.nix 的 home 文件
home.username = "qnnet";
home.homeDirectory = "/home/qnnet";
# =====================
# dotfiles 管理
# =====================
home.file = {
# 通过 home.file 直接生成文件内容
".config/alacritty/alacritty.yml".text = ''
font:
normal:
family: "JetBrains Mono"
size: 12
colors:
primary:
background: '#1e1e2e'
foreground: '#cdd6f4'
'';
".gitconfig".text = ''
[user]
name = QNNet
email = qnnet@example.com
[alias]
lg = log --oneline --graph --decorate
st = status -sb
'';
};
# =====================
# Shell 配置
# =====================
programs.zsh = {
enable = true;
enableCompletion = true;
plugins = with pkgs; [
{ name = "zsh-autosuggestions"; src = fetchFromGitHub {...}; }
{ name = "zsh-syntax-highlighting"; src = fetchFromGitHub {...}; }
];
initExtra = ''
# 自定义 Zsh 配置
export EDITOR=vim
alias ll='ls -lah'
'';
};
programs.fish = {
enable = true;
interactiveShellInit = ''
set -g EDITOR vim
function ll; ls -lah $argv; end
'';
};
# =====================
# Neovim 配置(用 Nix 管理插件和配置)
# =====================
programs.neovim = {
enable = true;
plugins = with pkgs.vimPlugins; [
nvim-lspconfig
nvim-treesitter
telescope-nvim
nvim-cmp
];
configure = {
customRC = ''
set number
set relativenumber
colorscheme catppuccin
'';
};
};
# =====================
# GitHub CLI
# =====================
programs.gh = {
enable = true;
settings = {
git_protocol = "ssh";
prompt = "enabled";
};
};
# =====================
# 环境变量
# =====================
home.sessionVariables = {
EDITOR = "vim";
VISUAL = "vim";
LANG = "zh_CN.UTF-8";
XDG_CONFIG_HOME = "$HOME/.config";
};
# 让 home-manager 生成激活脚本
home.stateVersion = "24.05";
}
这个配置的好处:它不依赖任何外部同步工具。任何人 clone 了这个配置仓库,运行 home-manager switch,就能在任意 NixOS 机器上重建完全相同的用户环境。
6.2 多机器 dotfiles 同步
home-manager 天然支持多机器配置差异化:
# home/qnnet.nix
{ inputs, pkgs, lib, ... }:
{
# 基础配置(所有机器共享)
home.username = "qnnet";
# 通过 specialArgs 传递机器标识
# 在 flake.nix 中调用时传入:
# home-manager.users.qnnet = import ./home/qnnet.nix;
programs.starship = {
enable = true;
settings = {
format = "$username$directory$git_branch$python$cmd_duration$line_break$status";
};
};
programs.wezterm = {
enable = true;
font = lib.mkIf (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
# Linux 上用 JetBrains Mono
font = "JetBrains Mono:style=Regular";
};
font = lib.mkIf (pkgs.stdenv.hostPlatform.system == "aarch64-darwin") {
# macOS 上用 SF Mono
font = "SF Mono";
};
};
}
七、实战:从零搭建 NixOS 开发环境
7.1 安装:Determinate Nix Installer
传统 Nix 安装在 macOS/Linux 多用户模式下需要配置 daemon,Determinate Nix Installer 把这个过程简化到一条命令:
# macOS / Linux 通用安装
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# 或者指定安装 channel
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/pr/3.21.2 | sh
# 安装后自动启用 flakes(不再需要手动配置 experimental-features)
nix --version # 应该是 2.18+
7.2 从 flake 初始化项目
# 创建一个 NixOS 配置仓库
mkdir ~/nixos-config && cd ~/nixos-config
git init
# 从模板创建(推荐)
nix flake init
# 目录结构
tree
# .
# ├── flake.lock
# ├── flake.nix
# ├── hosts/
# │ ├── laptop/
# │ │ └── configuration.nix
# │ └── server/
# │ └── configuration.nix
# ├── home/
# │ └── qnnet.nix
# └── modules/
# └── nginx-site.nix
7.3 第一个 NixOS 配置
# hosts/laptop/configuration.nix
{ inputs, config, pkgs, ... }:
{
imports = [
# 导入 nixpkgs 的硬件检测配置
<nixpkgs/nixos/modules/profiles/qemu-guest.nix>
# 如果是物理机:运行 nixos-generate-config 自动生成
./hardware-configuration.nix
];
# 桌面环境(可以选择 GNOME/KDE/Xfce/Sway 等)
services.xserver = {
enable = true;
displayManager.gdm.enable = true;
desktopManager.gnome.enable = true;
};
# 电源管理和显卡
powerManagement = {
enable = true;
cpuFreqGovernor = "powersave";
};
hardware.nvidia = {
enable = true;
modesetting.enable = true;
powerManagement.enable = false; # NVIDIA 独显
};
# 系统升级策略
system.autoUpgrade = {
enable = true;
dates = "04:00";
flake = "path:."; # 每天自动用 flake 更新
flags = [ "--accept-flake-config" ];
};
# 完整配置...
}
7.4 切换并激活
# 预览构建(不激活)
sudo nixos-rebuild build --flake .#laptop
# 激活新配置(当前会话切换)
sudo nixos-rebuild switch --flake .#laptop
# 更新 flake 依赖到最新
nix flake update
# 查看构建差异
nix-env --query --available --profile /nix/var/nix/profiles/system --compare-versions
八、性能优化:Nix 的构建与存储
8.1 二进制缓存:让构建几乎不需要
Nix 最大的痛点是编译时间——从源码构建所有包需要数小时。但 Nix 的解决方案优雅且高效:二进制缓存(Binary Cache)。
# 启用 cachix(最大的 Nix 二进制缓存服务)
nix-env -iA nixpkgs.cachix
# 使用公共缓存
cachix use nix-community
# 或者在 flake.nix 中自动配置
# 在 outputs 中添加:
# nixConfig.substituters = ["https://nix-community.cachix.org"];
# nixConfig.trusted-public-keys = ["nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="];
# 查看缓存命中率
nix-store --query --stats /run/current-system
cachix 的工作原理:如果 Nix 要构建一个 derivation,但它在远程缓存中已经存在(通过 derivation 的输入哈希标识),就直接下载预编译的产物,而不是从源码构建。Nix 社区维护了多个公共缓存,覆盖了绝大多数常用包。
8.2 并行构建
# 在 configuration.nix 中配置并行构建
nix.settings = {
max-jobs = "auto"; # 自动使用所有 CPU 核心
cores = 0; # 0 = 无限制
substituters = [
"https://nix-community.cachix.org"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
8.3 垃圾回收
# 查看 store 大小
nix-store --query --size /run/current-system
# 查看可回收的空间
nix-store --gc --print-dead
# 清理旧代数引用的包(保守清理)
sudo nix-collect-garbage --delete-old
# 激进清理(清理所有不可达包)
sudo nix-collect-garbage -d
# 保留最近的 3 个代数
sudo nix-collect-garbage --delete-older-than 3d
九、NixOS 在 2026 年的工程适用场景
9.1 适用场景
最适合 NixOS/Nix 的场景:
- 多设备开发环境一致性:你有 macOS 笔记本 + Linux 服务器 + WSL 工作站,三个环境的开发工具版本统一管理
- CI/CD 流水线:用 Nix 构建完全一致的开发/测试/生产镜像,无需 Docker layer 层叠
- 数据科学团队:Python/R/Julia 环境锁定,避免「numpy 版本导致结果不同」的科学计算噩梦
- DevOps/SRE:声明式基础设施配置,配置变更可审计、可回滚
- 个人 dotfiles 管理:新机器上跑一条命令,完全重建工作环境
9.2 局限性
NixOS 不是银弹,以下场景要谨慎:
- NVIDIA 闭源驱动:NixOS 对 NVIDIA 驱动的支持需要额外的模块配置,Wayland + NVIDIA 组合尤其折腾
- 企业软件依赖:某些商业软件(钉钉、某些 VPN 客户端)只提供 deb/rpm 包,不兼容 NixOS
- 学习曲线:Nix 语言的调试比传统 Shell 脚本困难得多,错误信息有时不够友好
- 社区文档质量:Nix 社区文档虽然丰富,但组织方式不够友好,新手容易迷路
9.3 2026 年的生态工具链
| 工具 | 用途 | 成熟度 |
|---|---|---|
| Determinate Nix Installer | 一键安装 Nix | ⭐⭐⭐⭐⭐ |
| devenv.sh | 开发环境管理 | ⭐⭐⭐⭐⭐ |
| home-manager | 用户配置管理 | ⭐⭐⭐⭐ |
| nix-darwin | macOS 上的 NixOS | ⭐⭐⭐ |
| cachix | 二进制缓存 | ⭐⭐⭐⭐⭐ |
| lorri | 开发目录守护进程 | ⭐⭐⭐ |
| direnv | 环境目录切换 | ⭐⭐⭐⭐ |
| nan.nvim | Neovim 的 Nix LSP | ⭐⭐⭐ |
十、总结:声明式配置的时代意义
10.1 从 NixOS 看基础设施的未来
NixOS 代表的不是一种操作系统,而是一种系统管理的思想实验。它的成功证明了几个重要的工程命题:
第一,「配置即代码」不仅是 SRE 的口号,它可以在语言层面得到原生支持。Nix 语言的一切皆表达式、引用透明、延迟求值,让系统规格变成了一段可分析、可测试、可版本化的程序。
第二,不可变性(Immutability)是可靠性的朋友。NixOS 的包不可覆盖、系统代数可回滚,这个设计思想正在被越来越多的工具采纳:Docker 的层不可变镜像、React 的不可变状态、Git 的不可变提交历史。
第三,生态系统的组合性比单体架构更强大。flake 的输入-输出模型,让整个 Nix 生态变成了一个可插拔的系统。你不需要 fork nixpkgs,只需要写一个 overlay,就能引入任意新的包和配置。
10.2 给工程师的建议
如果你今天想尝试 NixOS/Nix,建议从以下路径开始:
- 第一周:在一台虚拟机或备用设备上安装 NixOS(不要拿主力机冒险)
- 第二周:用 flakes 管理你的 dotfiles(home-manager),体验声明式 dotfiles
- 第三周:用 devenv.sh 为一个现有项目构建开发环境
- 第四周:把 NixOS 配置 Git 化,加入团队协作流程
** NixOS 不是一个你「学会」然后「用着」的技能,它是一个你「习惯」然后「离不来」的操作系统哲学。**
参考资源:
- NixOS 官方文档:https://nixos.org/manual/nix/stable/
- NixOS Wiki:https://nixos.wiki/
- Determinate Systems Blog:https://determinate.systems/posts/
- devenv.sh 文档:https://devenv.sh/
- home-manager 手册:https://nix-community.github.io/home-manager/
- NixOS 中文社区:https://nixos-cn.org/