Nix 2026 深度实战:当声明式遇上不可变——一次把函数式包管理与可重现开发环境讲透
你是否曾经遇到过这样的场景:新来的同事 clone 了项目,跑了半天装不上依赖,环境配置写了一整页 README 还是漏了什么;CI 上跑得好好的,本地就是跑不通;换了台机器,整整两天在配环境。Docker 解决了「我的代码在你机器上跑不了」,但它没有解决「我的开发环境本身就不一致」。
这个问题,我们叫它「环境碎片化」。Nix 给出的答案是:不要管理命令,要管理结果。
一、背景:从「装软件」到「管理系统的数学证明」
1.1 传统包管理器的问题
传统包管理器(apt、yum、brew、pip)有一个根本性的问题:它们管理的是「动作」,而不是「状态」。
当你执行 apt install nginx 时,系统做的是:
- 检查依赖
- 下载二进制包
- 解压到
/usr - 写入配置文件
- 启动服务
这个过程是有副作用的。如果两个项目依赖同一个库的不同版本,传统包管理器要么报错,要么让其中一个覆盖另一个。你没有办法同时让 nginx 1.20 和 nginx 1.22 都装在你的系统里,并且让它们互不干扰。
这就是著名的「依赖地狱」(Dependency Hell)。
1.2 Nix 的破局思路
Nix 是怎么解决这个问题的?它的核心思想非常反直觉:
不要把文件写到系统目录,而是写到 Nix Store。
Nix Store 是一个以内容哈希命名的目录:
/nix/store/gc3a4b6q2j8k1m9p3r5t7v0z-xz-nginx-1.22.1/
注意这个路径:gc3a4b6q2j8k1m9p3r5t7v0z-xz-nginx-1.22.1
这个哈希不是版本号,而是整个依赖链的内容哈希。也就是说:
- 同一个软件,如果它的依赖不同,路径就不同
- 你可以同时安装 nginx 链接到 glibc 2.31 和 nginx 链接到 glibc 2.35,它们是两个完全独立的目录
- 卸载一个不影响另一个
这种设计叫做 「不可变输出,可变入口」。真正的软件包永远是只读的,只有 Profile(类似软链接的层)负责把它们「组合」起来呈现给用户。
1.3 为什么 2026 年 Nix 值得关注
2026 年的开发者工具生态有几个明显趋势:
- Devcontainer 的局限:Docker 虽然解决了部署问题,但开发体验并不好——构建慢、调试麻烦、文件系统性能差
- 多语言项目的环境管理碎片化:Node.js 有 nvm/npm,Python 有 pyenv/conda/poetry,Rust 有 rustup,每个人机器上版本都不一样
- GitOps 和 IaC 的成熟:开发者已经接受了「代码即基础设施」,但开发环境本身还没有被代码化
- Nix 生态的成熟:
- Flakes 已经稳定,成为主流用法
- devenv.nix 让项目级环境定义变得简单
- pre-commit-hooks.nix 解决了 CI 一致性问题
- NixOS 在 Linux 发行版中稳定增长
Nix 解决的是所有这些问题的一个公约数:「让任何环境在任何机器上以完全相同的方式运行」。
二、核心概念:Nix 的函数式世界观
2.1 一切皆是 Derivation
在 Nix 的世界里,安装一个包 == 声明一个 Derivation(构建动作)。
# 最简单的 Nix 表达式:一个 derivation
{ stdenv }:
stdenv.mkDerivation {
pname = "my-hello";
version = "1.0.0";
src = ./src;
buildInputs = [ stdenv ];
buildPhase = ''
echo "Building from $src"
gcc -o hello hello.c
'';
installPhase = ''
mkdir -p $out/bin
cp hello $out/bin/
'';
}
这里的 mkDerivation 是 Nix 提供的构建原语。注意 $out 这个变量:它是 Nix 指定的输出路径,由内容哈希自动生成。你不需要指定它,Nix 自动算出来。
当 Nix 真正执行这个 derivation 时,它会:
- 计算
$out路径(基于输入的内容哈希) - 在沙箱中执行
buildPhase - 把
$out/bin/hello写入/nix/store/xxx-hello-1.0.0/bin/hello
函数式体现在哪里? 对于相同的输入(src 内容、依赖版本),Nix 保证输出一致。如果你想让两个人构建出完全相同的环境,只需要确保他们的 src 和 buildInputs 完全相同——而这可以通过 Flakes 的 locked 版本精确控制。
2.2 懒求值:Nix 的性能核心
Nix 表达式是惰性求值的。这意味着当你写:
let
pkgs = import <nixpkgs> {};
vim = pkgs.vim;
emacs = pkgs.emacs;
in
vim # emacs 永远不会被构建或下载
即使 vim 和 emacs 都在 pkgs 里,只有 vim 被真正用到时,Nix 才会去构建/下载它。这让 Nix 可以定义巨大的软件仓库而不产生任何性能开销。
2.3 Nix 语言快速入门
Nix 语言的特点是:纯函数式、惰性求值、动态类型、类 Shell 语法。不需要学很久就能写实用的东西。
# 变量和字符串
let
name = "world";
greeting = "Hello, ${name}!";
in
greeting # => "Hello, world!"
# 函数
map (x: x * 2) [1 2 3 4 5] # => [2 4 6 8 10]
# 属性集合(类似 dict/JSON)
let
user = {
name = "Alice";
age = 30;
skills = ["Rust" "Go" "Python"];
};
in
user.name # => "Alice"
# 条件表达式
let
score = 85;
grade = if score >= 90 then "A" else if score >= 80 then "B" else "C";
in
grade # => "B"
# With 语句:把属性集合的属性带入作用域
with (import <nixpkgs> {}); [
curl
git
ripgrep
fd
] # 直接用 pkgs 里包的名字,不用写 pkgs.curl
Nix 的属性集合(AttrSet)是整个 Nixpkgs 的基础:每个包、每个配置项都是一个 AttrSet,通过 . 访问深层属性,通过 // 合并两个集合。
# 合并配置
let
base = { port = 8080; debug = false; };
override = { port = 3000; verbose = true; };
in
base // override # => { port = 3000; debug = false; verbose = true; }
这个 // 合并操作在 Nixpkgs 中到处都在用:覆盖默认包参数只需要 pkgs.vim // { wrapPylance = true; }。
三、Flakes:Nix 的现代化里程碑
3.1 什么是 Flakes,为什么你需要它
Flakes 是 2020 年引入的 Nix 功能,目的是解决传统 Nix 的一些长期痛点:
问题一:无法锁定依赖版本
传统 nix-env 安装的包,版本由你运行时的 nixpkgs 决定。一个月后重装,nixpkgs 已经更新,可能装到新版本。
问题二:Nix 表达式不是标准化的
每个人的 default.nix 写法都不一样,没有标准入口,没有版本约束。
问题三:无法组合多个 Nix 表达式
项目 A 用的是 nixpkgs-23.05,项目 B 用的是 nixpkgs-unstable,想在同一个系统里共存很麻烦。
Flakes 通过引入标准化的 flake.nix 文件格式 解决了所有这些问题。
3.2 Flake 的标准结构
# flake.nix
{
description = "My awesome project development environment";
# 输入:声明依赖及其版本源
inputs = {
# Nixpkgs:不指定分支则用 nixpkgs-unstable
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# 系统定义包
systems.url = "github:nix-systems/default";
# devenv:开发环境专用工具
devenv.url = "github:cachix/devenv/nixos-unstable";
# Rust overlay(获取最新 Rust 工具链)
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
};
# 输出:定义 flake 能提供的所有东西
outputs = { self, nixpkgs, systems, devenv, rust-overlay, ... }@inputs:
let
# 为每个系统生成输出
forEachSystem = nixpkgs.lib.genAttrs (import systems);
in
{
# 1. 开发环境(nix develop 进入)
devShells = forEachSystem (system: {
default = self.packages.${system}.devShells.default;
});
# 2. 可安装的包
packages = forEachSystem (system: {
default = self.packages.${system}.my-app;
my-app = self.packages.${system}.my-app;
});
# 3. formatter = forEachSystem (system: {
# nixpkgs.legacyPackages.${system}.alejandra
# });
# 4. devShells(显式定义,方便其他 flake 引用)
devShells = forEachSystem (system: {
default =
let
pkgs = nixpkgs.legacyPackages.${system};
# 合并 Rust overlay
overlays = [ rust-overlay.overlays.default ];
pkgs' = import nixpkgs {
system = system;
overlays = overlays;
};
in
pkgs'.mkShell {
# 进入环境时自动加载的包
packages = with pkgs'; [
# 基础工具
git
gh
direnv
fd
ripgrep
# 项目专用工具
rustc
cargo
rust-analyzer
clippy
# Node.js
nodejs_22
pnpm
# 数据库
postgresql_16
# 其他语言
python311
go_1_23
];
# 进入环境时执行的钩子
shellHook = ''
echo "=== Development Environment ==="
echo "Project: my-awesome-app"
echo "Rust: $(rustc --version)"
echo "Node: $(node --version)"
echo "PostgreSQL: $(psql --version)"
echo "=============================="
# 加载 direnv
if [ -f .envrc ]; then
echo "Loading .envrc..."
direnv allow .
fi
'';
};
});
# 5. NixOS 配置(如果你的项目包含 NixOS 配置)
# nixosConfigurations = { ... };
# 6. 允许其他 flake 引用你
hydraJobs = self.packages;
};
}
3.3 flake.lock:版本锁定的核心
当你第一次运行 nix flake check 或 nix develop 时,Nix 会自动生成 flake.lock 文件:
{
"version": 7,
"outputs": {
"nixpkgs": {
"type": "github",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c7c7e3f2a0b8c2e8d1f4a6b9c0e3d2f1a8b7c6d5",
"hash": "sha256:1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t="
},
"rust-overlay": {
"type": "github",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
"hash": "sha256:9a8b7c6d5e4f3g2h1i0j9k8l7m6n5o4p3q2r1s0t9u="
}
}
}
这个文件由 Nix 自动维护,不应该手动编辑。当你 git pull 别人的代码,flake.lock 会确保你和作者用完全相同的依赖版本。
3.4 Flake 命令速查
# 进入开发环境
nix develop
# 构建项目(不带 GUI 的 headless 环境)
nix build
# 运行包
nix run nixpkgs#hello
# 更新 flake 输入到最新版本
nix flake update
# 查看可用的输出
nix flake show
# 检查 flake 是否有语法错误
nix flake check
# 格式化 flake.nix
nix fmt
# 在临时环境中运行命令(不污染当前环境)
nix run nixpkgs#python311 -- -c "print('hello')"
# 列出可用的包
nix search nixpkgs postgresql
四、开发环境实战:从零到生产
4.1 devenv:用 Nix 定义项目环境
devenv 是一个专门为项目开发环境设计的工具。它让你用 YAML/Nix 定义项目的依赖,一次配置,全团队一致。
# devenv.yaml
# 官方推荐的项目环境配置文件格式
version = 2
# 进入环境时自动启动的服务
services.postgres = {
enable = true;
port = 5432;
version = 16;
settings = {
max_connections = 100;
shared_buffers = "256MB";
};
initialDatabases = [{ name = "myapp_dev"; }];
initialScript = ./scripts/init-db.sql;
};
services.redis = {
enable = true;
port = 6379;
maxmemory = "256mb";
appendonly = true;
};
# 编程语言和环境
languages.javascript = {
enable = true;
package = pkgs.nodejs_22;
};
languages.rust = {
enable = true;
channel = "stable";
};
languages.python = {
enable = true;
package = pkgs.python311;
venv = {
enable = true;
autoInit = true;
};
};
# 额外工具
packages = with pkgs; [
gh
git
direnv
fd
ripgrep
entr
watchexec
];
# 环境变量
env.GOOGLE_CLOUD_PROJECT = "my-project-dev";
env.NODE_ENV = "development";
# 启动钩子
enterShell = ''
echo "Welcome to myapp dev environment!"
if ! command -v myapp &> /dev/null; then
echo "Building myapp..."
cargo build
fi
'';
# 进入 devenv 环境
devenv up
# 等价于
nix develop .#devenv
devenv up 会:
- 启动 PostgreSQL 服务(后台)
- 启动 Redis 服务(后台)
- 配置好 Node.js、Rust、Python 环境
- 进入 shell,自动运行 enterShell
对于团队来说,只需要把 devenv.yaml 提交到代码库,新成员 clone 后运行 devenv up,5 秒后就有完全一致的开发环境。
4.2 direnv:自动化的目录级环境切换
direnv 配合 Nix 是绝配。它的作用是:当你 cd 进入某个目录时,自动加载对应的环境变量;离开时自动卸载。
在项目根目录创建 .envrc:
# .envrc
# 加载 devenv 环境
use devenv
# 首次使用
direnv allow .
# 之后每次进入目录,direnv 自动加载
cd my-project
# [direnv] loading .envrc
# 你的 PATH 里就自动有了 cargo、node、psql 等工具
这解决了一个长期困扰开发者的问题:不同的项目有不同的环境依赖,我不想每次都手动激活,也不想全局安装冲突的版本。
4.3 Pre-commit Hooks 与 CI 一致性
Git Hooks 是代码质量的好帮手,但配置各团队不同。pre-commit-hooks.nix 把 Nix 和 pre-commit.com 生态结合起来:
# devenv.yaml 中添加
hooks = {
enable = true;
# Nix 相关
nixfmt.enable = true; # 格式化 Nix 代码
deadnix.enable = true; # 检测未使用的 Nix 代码
statix.enable = true; # Nix 静态分析
# 通用
trailing-whitespace.enable = true;
end-of-file-fixer.enable = true;
check-merge-conflicts.enable = true;
# Rust
rustfmt.enable = true;
clippy.enable = true;
# Python
ruff.enable = true; # ruff 比 flake8 快 10 倍
mypy.enable = true;
# TypeScript
biome.enable = true; # 比 eslint 快得多
};
在 CI 中运行(非 Flake 项目):
# .github/workflows/quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v26
with:
install-url: https://install.determinate.systems/nix
- run: nix develop --accept-flake-config -c bash -c '
nix fmt --check
cargo fmt --check
cargo clippy --all-targets -- -D warnings
'
所有工具的版本都由 Nix 锁定,CI 环境和本地环境完全一致。
4.4 私有包仓库:个人/团队的 Nix 覆盖层
你也可以把 Nix 用于私有项目的包管理:
# my-org/flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs, ... }:
let
forEachSystem = system:
let
pkgs = import nixpkgs { system = system; };
in
{
packages = {
# 公开包(来自 nixpkgs)
inherit (pkgs) nginx postgresql;
# 私有包(自建 derivation)
my-internal-tool = pkgs.callPackage ./packages/my-internal-tool { };
# 私有服务的 Docker 镜像(打包)
my-service-image = pkgs.dockerTools.buildImage {
name = "my-org/my-service";
config.Cmd = ["${pkgs.bash}/bin/bash" "-c" "my-service start"];
extraCommands = ''
mkdir -p $out/bin
cp ${./binaries/my-service} $out/bin/my-service
'';
};
};
};
in
{
packages.x86_64-linux = forEachSystem "x86_64-linux";
packages.aarch64-darwin = forEachSystem "aarch64-darwin";
};
}
团队成员只需要:
# 添加团队 flake 到 inputs
nix registry add my-org github:my-org/my-org-flake
# 然后就可以用了
nix run my-org#my-internal-tool
nix build my-org#my-service-image
五、NixOS:把整个操作系统声明化
5.1 为什么操作系统也需要声明式管理
NixOS 是基于 Nix 的 Linux 发行版。它的核心思想是:整个操作系统配置就是一个 Nix 表达式。
传统 Linux 系统的配置是分散的:
- 网络配置在
/etc/network/interfaces或 NetworkManager - 服务在
/etc/systemd/system/ - 内核参数在
/etc/sysctl.conf - 定时任务在
/etc/crontab
出了问题,你要查很多地方。而且重建环境(换机器、重装)非常麻烦。
NixOS 把这一切统一到一个配置文件:
# /etc/nixos/configuration.nix
{ config, pkgs, ... }:
{
imports = [
# 包含硬件配置(由 nixos-generate-config 自动生成)
./hardware-configuration.nix
];
# ========================
# 网络
# ========================
networking.hostName = "my-dev-machine";
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 22 80 443 3000 ];
# ========================
# 用户与权限
# ========================
users.users.qnnet = {
isNormalUser = true;
description = "Developer";
extraGroups = [ "wheel" "docker" "networkmanager" ];
shell = pkgs.fish; # 用 Fish 作为默认 shell
};
# ========================
# 桌面环境(GNOME + 自定义)
# ========================
services.xserver = {
enable = true;
desktopManager.gnome = {
enable = true;
extensions = with pkgs.gnomeExtensions; [
Vitals
SoundInputDeviceSelector
];
};
displayManager.gdm.enable = true;
# 键盘布局
xkb = {
layouts = ["us" "cn"];
options = ["grp:alt_shift_toggle"];
};
};
# ========================
# 开发工具(全局安装)
# ========================
environment.systemPackages = with pkgs; [
git
gh
neovim
tmux
fish
curl
wget
htop
btop
nix-index # 快速搜索 nixpkgs 中的包
];
# ========================
# 系统服务
# ========================
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
settings = {
max_connections = 200;
shared_buffers = "2GB";
effective_cache_size = "6GB";
};
};
services.docker = {
enable = true;
autoPrune.enable = true;
daemon.settings = {
"log-driver" = "json-file";
"log-opts" = { "max-size" = "100m"; "max-file" = "3"; };
};
};
# ========================
# 计划任务
# ========================
systemd.timers.auto-upgrade = {
wantedBy = ["timers.target"];
timerConfig.OnCalendar = "daily";
};
systemd.services.auto-upgrade = {
serviceConfig.ExecStart = "${pkgs.nix}/bin/nix-channel --update";
};
# ========================
# 内核与引导
# ========================
boot.kernelPackages = pkgs.linuxPackages_latest;
boot.loader.grub = {
enable = true;
device = "/dev/sda";
configurationLimit = 10; # 保留最近 10 个启动项
};
# ========================
# nix 配置
# ========================
nix = {
settings = {
auto-optimise-store = true;
experimental-features = ["nix-command flakes"];
substituters = [
"https://nix-community.cachix.org"
"https://cuda-packages.nvidia.com"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9ql2s3NQ8LCWzcmBKzVELGmh5m2Kj2R2N5Hs="
"cuda-packages.nvidia.com-7PvmdKKuTguM4KLibxe5TygOxLUjWz7p6S9Gz6OVK9E="
];
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
# 应用 NixOS 更新
system.stateVersion = "24.11";
}
应用配置:
# 测试配置是否正确(dry-run)
sudo nixos-rebuild test --dry-run
# 应用配置
sudo nixos-rebuild switch --upgrade
# 查看历史配置,回滚
sudo nixos-rebuild list-generations
sudo nixos-rebuild switch --profile-name=1 # 回滚到上一代
NixOS 的核心优势:原子升级 + 可回滚
每次 nixos-rebuild switch,Nix 都会生成一个新的系统配置,并将其加入 GRUB 引导菜单。如果新配置导致系统无法启动,只需要重启后在 GRUB 中选择上一代配置,就能恢复到正常工作状态。
Generation 45: 2026-07-14 09:30:00 ← 当前
Generation 44: 2026-07-10 14:22:00 ← 可回滚
Generation 43: 2026-07-01 18:00:00
Generation 42: 2026-06-28 12:00:00 ← Docker 配置有问题,回滚到这里
5.2 NixOS 模块系统:配置组合的魔法
NixOS 的模块系统允许你把配置拆分成多个文件,然后在 imports 中组合:
/etc/nixos/
├── configuration.nix # 主入口
├── hardware-configuration.nix # 硬件自动生成
├── modules/
│ ├── gaming.nix # 游戏配置(NVIDIA driver、游戏工具)
│ ├── docker.nix # Docker 配置
│ ├── rust-dev.nix # Rust 开发工具
│ └── ml-dev.nix # 机器学习环境(CUDA、cuDNN)
└── secrets/
└── age.nix # 加密的敏感信息
# modules/rust-dev.nix
{ config, pkgs, ... }:
{
# 这个模块只在启用时生效
config = pkgs.lib.mkIf config.devenv.rust.enable {
environment.systemPackages = with pkgs; [
rustc
cargo
rust-analyzer
rustfmt
clippy
cargo-watch
rustup
];
# 自动安装 rustup 工具链
systemd.services.rustup-init = {
description = "Initialize rustup toolchains";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.rustup}/bin/rustup default stable";
RemainAfterExit = true;
};
};
};
# 声明这个模块提供的选项
options.devenv.rust.enable = pkgs.lib.mkOption {
type = pkgs.lib.types.bool;
default = false;
description = "Enable Rust development environment";
};
}
这让你可以在主配置中选择性地启用模块:
# configuration.nix
{ config, pkgs, inputs, ... }:
{
imports = [
./hardware-configuration.nix
./modules/rust-dev.nix
./modules/docker.nix
];
# 启用 Rust 开发模块
devenv.rust.enable = true;
# 或在运行时临时启用
# nixos-rebuild switch -I nixos-config=./modules/gaming.nix
}
六、性能优化:让 Nix 飞起来
6.1 缓存策略
Nix 最大的性能问题是首次构建。没有缓存的情况下,从源码编译所有依赖可能需要数小时。正确的做法是充分利用缓存。
# /etc/nixos/configuration.nix 中的缓存配置
nix = {
settings = {
# 启用二进制缓存
substituters = [
# NixOS 官方缓存
"https://nix-community.cachix.org"
# NVIDIA CUDA 缓存(GPU 开发必备)
"https://cuda-packages.nvidia.com"
# Cachix 个人/团队缓存
"https://my-org.cachix.org"
];
# 对应缓存的公钥
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9ql2s3NQ8LCWzcmBKzVELGmh5m2Kj2R2N5Hs="
"cuda-packages.nvidia.com-7PvmdKKuTguM4KLibxe5TygOxLUjWz7p6S9Gz6OVK9E="
"my-org.cachix.org-1:AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefghijklmnopqr="
];
};
};
设置自己的 Cachix 缓存(免费账号可用):
# 安装 cachix
nix-env -iA nixpkgs.cachix
# 登录
cachix authtoken # 从 cachix.org 获取 token
# 使用团队缓存
cachix use my-org
# 推送到自己的缓存
cachix push my-org $(nix-store --query --requisites /nix/var/nix/profiles/system | head -1000)
6.2 并行构建与 RAM 优化
# 充分利用 CPU 核心
nix.settings.max-jobs = "auto"; # 自动使用所有 CPU 核心
nix.settings.cores = 0; # 0 = 不限制,用尽所有
# SSD 缓存优化(将 /nix/store 放到 SSD)
nix.settings.sandbox = true; # 沙箱隔离构建
nix.settings.build-users-group = "nixbld"; # 多用户并行构建
# RAM 紧张时的设置(避免同时跑太多构建)
nix.settings.max-jobs = 4; # 最多同时构建 4 个包
nix.settings.keep-outputs = false; # 不保留构建中间产物(节省空间)
nix.settings.keep-derivations = false; # 不保留 derivation 记录
6.3 Nix 与 Docker 的组合拳
Nix 构建出来的可执行文件是可以直接用到 Docker 镜像里的:
# Dockerfile
FROM nixbase:3.0 # 包含 Nix 的最小镜像(~15MB)
# 只复制必要的文件
COPY flake.lock flake.nix ./
COPY src ./src
COPY Cargo.toml Cargo.lock ./
# 用 Nix 构建(利用 Docker 层缓存)
RUN nix build .#my-app
# 最终镜像只有编译好的二进制
FROM ubuntu:24.04
COPY --from=0 /nix/var/nix/derivations/*/result/bin/my-app /usr/local/bin/
ENTRYPOINT ["my-app"]
但更优雅的做法是用 pkgs.dockerTools:
# 直接在 Nix 中构建 Docker 镜像
my-docker-image = pkgs.dockerTools.buildImage {
name = "my-org/my-service";
tag = "latest";
config = {
Cmd = ["${my-service}/bin/my-service"];
Env = [
"DATABASE_URL=postgres://user:pass@localhost:5432/db"
"RUST_LOG=info"
];
Expose = {
"3000/tcp" = {};
};
Labels = {
"org.opencontainers.image.source" = "https://github.com/my-org/my-service";
"org.opencontainers.image.revision" = self.rev or "dev";
};
};
# 把依赖项加入镜像
extraCommands = ''
mkdir -p $out/bin
cp ${my-service}/bin/my-service $out/bin/my-service
mkdir -p $out/lib
cp ${pkgs.postgresql_16}/lib/libpq.so* $out/lib/
'';
# 运行用户
runAsRoot = ''
#!${pkgs.bash}/bin/bash
set -e
${pkgs.dockerTools.shadowSetup}
groupadd -r myservice
useradd -r -g myservice myservice
'';
};
构建和导出:
nix build .#my-docker-image
docker load < result
docker run -p 3000:3000 my-org/my-service:latest
七、与其他工具的对比
7.1 Nix vs Docker
| 维度 | Nix | Docker |
|---|---|---|
| 粒度 | 包级/文件级 | 容器级 |
| 叠加能力 | 可以叠加任意多个包版本 | 一个镜像包含所有 |
| 可重现性 | 内容哈希,100% 可重现 | 依赖构建步骤描述 |
| 存储效率 | Store 中相同内容只存一份 | 每层独立 |
| 开发体验 | 原生 shell,直接调试 | 需要 exec 进入容器 |
| 系统级配置 | 支持 NixOS 全系统 | 不涉及系统配置 |
| Windows 支持 | 有限 | 完整(WSL) |
| 启动速度 | 即时(无容器开销) | 取决于镜像大小 |
结论:Docker 擅长「打包应用」,Nix 擅长「管理环境和依赖」。两者不是互斥的,很多团队用 Nix 管理开发环境,用 Docker 打包生产应用。
7.2 Nix vs Homebrew / Conda
Homebrew 和 Conda 的问题:
- 版本隔离差:你只能装一个版本的 Python(除非用 conda env)
- 依赖解析不严格:可能装了 A 包导致 B 包出问题
- 卸载不干净:删了主包但依赖可能残留
Nix 的优势:
- 任意多版本共存:Python 3.8/3.9/3.10/3.11 可以同时装
- 卸载无残留:Store 中没有引用就 GC 回收
- 跨语言统一:Python、Node、Rust、Go 用同一套工具管理
7.3 Nix vs Direnv + pyenv/nvm/rustup
这些工具链各自管各自的,而 Nix 是统一的:
# 传统方式:N 个工具,每个管理自己的版本
pyenv install 3.11.0
pyenv local 3.11.0
nvm install 20
nvm use 20
rustup default stable
# Nix 方式:一次声明,全部搞定
nix develop # flake.nix 中声明的所有版本
八、避坑指南:Nix 实践中的常见陷阱
坑 1:Flake 功能未启用
症状:error: unknown flake attribute 'packages'
原因:Nix 默认不启用 Flakes。需要手动开启:
# 临时开启(当前 shell)
export NIX_CONFIG="experimental-features = nix-command flakes"
# 永久开启(Linux)
echo 'experimental-features = nix-command flakes' | sudo tee -a /etc/nix/nix.conf
sudo systemctl restart nix-daemon
# macOS
mkdir -p ~/.config/nix
echo 'experimental-features = nix-command flakes' > ~/.config/nix/nix.conf
坑 2:Store 膨胀
症状:磁盘空间急剧下降
原因:Nix 保留所有历史版本的包,除非手动 GC。
# 查看 Store 大小
nix path-info --closure-size /nix/store | sort -h | tail -20
# 手动 GC
nix-collect-garbage -d
# 自动 GC(NixOS 配置)
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
坑 3:nix-channel 与 Flakes 混用
症状:nix-env -iA nixpkgs.vim 后版本不一致
原因:传统 nix-env 和 Flakes 使用不同的包数据库。
解决:严格分开。Flakes 项目用 nix develop 和 nix build;全局工具用 home-manager(推荐)或严格使用 Flakes:
# 推荐:用 home-manager 管理全局用户环境
# home.nix
{ pkgs, ... }:
{
home.packages = with pkgs; [
vim
git
curl
];
programs.fish.enable = true;
programs.git.enable = true;
}
坑 4:构建超时
症状:复杂包的构建超时
原因:默认 sandbox 模式下网络访问受限。
# 允许网络访问的沙箱构建
nix build --option sandbox false .#my-package
# 或者在 flake.nix 中配置
nix.settings.sandbox = false; # 不推荐,仅调试用
坑 5:MacOS 下文件权限问题
症状:Operation not permitted 或 Nix 无法创建目录
原因:macOS SIP(系统完整性保护)限制。
解决:安装到用户目录而非系统目录:
# ~/.config/nix/nix.conf
experimental-features = nix-command flakes
substituters = https://cache.nixos.org https://nix-community.cachix.org
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcKtQ2U=
九、实战案例:把团队开发环境迁移到 Nix
案例背景
一个 5 人后端团队,项目包含:Go 服务(gRPC + Protobuf)、Rust CLI 工具、Python 数据处理脚本、Kafka 消息队列、PostgreSQL + Redis。项目新成员入职时,平均需要 2 天配环境。
迁移方案
Step 1:创建项目 flake.nix
# 项目根目录 flake.nix
{
description = "backend-platform: production-grade backend infrastructure";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default";
# Go 工具
gomod2nix = {
url = "github:nix-community/gomod2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
devenv = {
url = "github:cachix/devenv/nixos-unstable";
inputs.nixpkgs.follows = "nixpkgs";
};
# 数据库版本锁定
nixpgkgs-postgres = {
url = "github:NixOS/nixpkgs/23.11";
lastModified = "2024-04-15";
};
};
outputs = { self, nixpkgs, systems, devenv, ... }@inputs:
let
forAllSystems = nixpkgs.lib.genAttrs (import systems);
in
{
devShells = forAllSystems (system: {
default = self.devShells.${system}.default;
default = devenv.lib.mkShell {
inherit inputs;
root = ./.;
languages = {
go = {
enable = true;
version = "1.23";
tools = with pkgs; [
golangci-lint
goreleaser
protobuf
protoc-gen-go
protoc-gen-grpc-web
];
};
rust = {
enable = true;
channel = "stable";
};
python = {
enable = true;
package = pkgs.python311;
venv.enable = true;
};
};
services = {
kafka = {
enable = true;
port = 9092;
};
zookeeper = {
enable = true;
port = 2181;
};
postgres = {
enable = true;
port = 5432;
version = 16;
extensions = ["uuid-ossp" "pg_trgm" "vector"];
};
redis = {
enable = true;
port = 6379;
};
};
packages = with pkgs; [
# CLI 工具
kubectl
helm
k9s
skaffold
docker
docker-compose
# 效率工具
fd
ripgrep
fzf
jq
yq
gh
# CI 工具
act # 本地运行 GitHub Actions
];
env = {
COMPOSE_PROJECT_NAME = "backend-platform";
DOCKER_BUILDKIT = "1";
};
};
});
};
}
Step 2:新成员 onboarding 流程
原来(2 天):
1. 安装 Homebrew
2. brew install go@1.23
3. brew install python@3.11
4. pip install poetry
5. poetry install
6. 安装 Docker Desktop
7. 配置 Kafka
8. 手动装 Redis/PostgreSQL
9. 配 PATH(冲突!)
10. 解决「这个版本不对」的问题
现在(10 分钟):
git clone git@github.com:my-org/backend-platform.git
cd backend-platform
# 单行搞定(需要先安装 Nix)
sh <(curl -L https://nixos.org/nix/install)
# 进入开发环境(自动启动 Kafka/PostgreSQL/Redis)
nix develop
# 查看可用命令
devenv info
Step 3:CI 加速
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v26
with:
nix_path: nix_config = "experimental-features = nix-command flakes"
- name: Cache Nix store
uses: actions/cache@v4
with:
path: ~/.cache/nix
key: ${{ runner.os }}-nix-${{ hashFiles('flake.lock') }}
restore-keys: ${{ runner.os }}-nix-
- name: Lint & Type Check
run: nix develop -c cargo clippy --all-targets -- -D warnings
- name: Unit Tests
run: nix develop -c cargo test
- name: Integration Tests
run: |
nix develop -c devenv up &
sleep 10
nix develop -c cargo test --test integration
十、总结与展望
Nix 在 2026 年的定位
Nix 不是银弹。它有一个陡峭的学习曲线,文档质量参差不齐,debug 体验也不够友好。但它的核心价值——声明式、不可变、可重现——在当前这个多语言、多框架、多服务的开发环境中,比以往任何时候都更有价值。
什么时候用 Nix:
- 团队有跨语言项目(Rust + Python + Node.js + Go)
- 需要在多台机器上保持一致的 CI/CD 环境
- 团队成员用不同操作系统(Linux + macOS)
- 想把开发环境和生产环境的配置代码化
- 用 NixOS 作为服务器操作系统
什么时候暂时不用 Nix:
- 单人项目,工具链简单
- 团队成员完全不熟悉 Nix(学习成本要考虑)
- 需要 Windows 一流支持
- 只需要 Docker 就能解决的问题
生态趋势
从 2024 年到 2026 年,Nix 生态有几个明显的变化:
- Flakes 成为事实标准:
nixpkgs-unstable现在默认支持 Flakes,legacy方式正在逐步退出 - Devenv 降低门槛:从写
flake.nix到写devenv.yaml,体验提升明显 - Cachix 生态完善:越来越多的团队建立自己的二进制缓存,首次构建时间大幅缩短
- NixOS 在服务器端增长:DigitalOcean、GitHub Codespaces 等开始支持 Nix 环境
- IDE 支持改善:
nix-index+niv让 VSCode 和 Neovim 的 Nix LSP 支持更稳定
给团队的建议
如果你决定在团队中引入 Nix,建议分三步走:
第一阶段(1-2 周):先用 Nix 管理个人工具链。把 .zshrc/fish.nix 中的包管理迁移到 Nix,不影响其他同事。
第二阶段(2-4 周):为项目创建 devenv.yaml。让所有团队成员用同一套开发环境,验证可行性。
第三阶段(持续):逐步将 CI、服务配置、数据库初始化脚本等迁移到 Nix。用 Nix 替换 Dockerfile 中的构建步骤,最终实现「开发环境 = 轻量级 NixOS 配置」。
真正的技术自由,不是安装了更多的工具,而是让工具不再成为阻碍。
Nix 给你的,是一份可以 commit 到 Git 的环境配置——任何时间、任何机器、一个 git clone && nix develop,你就能回到写第一行代码时的工作状态。
这,才是真正的可重现性。