编程 Nix 2026 深度实战:当声明式遇上不可变——一次把函数式包管理与可重现开发环境讲透

2026-07-14 10:16:13 +0800 CST views 16

Nix 2026 深度实战:当声明式遇上不可变——一次把函数式包管理与可重现开发环境讲透

你是否曾经遇到过这样的场景:新来的同事 clone 了项目,跑了半天装不上依赖,环境配置写了一整页 README 还是漏了什么;CI 上跑得好好的,本地就是跑不通;换了台机器,整整两天在配环境。Docker 解决了「我的代码在你机器上跑不了」,但它没有解决「我的开发环境本身就不一致」。

这个问题,我们叫它「环境碎片化」。Nix 给出的答案是:不要管理命令,要管理结果

一、背景:从「装软件」到「管理系统的数学证明」

1.1 传统包管理器的问题

传统包管理器(apt、yum、brew、pip)有一个根本性的问题:它们管理的是「动作」,而不是「状态」

当你执行 apt install nginx 时,系统做的是:

  1. 检查依赖
  2. 下载二进制包
  3. 解压到 /usr
  4. 写入配置文件
  5. 启动服务

这个过程是有副作用的。如果两个项目依赖同一个库的不同版本,传统包管理器要么报错,要么让其中一个覆盖另一个。你没有办法同时让 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 年的开发者工具生态有几个明显趋势:

  1. Devcontainer 的局限:Docker 虽然解决了部署问题,但开发体验并不好——构建慢、调试麻烦、文件系统性能差
  2. 多语言项目的环境管理碎片化:Node.js 有 nvm/npm,Python 有 pyenv/conda/poetry,Rust 有 rustup,每个人机器上版本都不一样
  3. GitOps 和 IaC 的成熟:开发者已经接受了「代码即基础设施」,但开发环境本身还没有被代码化
  4. 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 时,它会:

  1. 计算 $out 路径(基于输入的内容哈希)
  2. 在沙箱中执行 buildPhase
  3. $out/bin/hello 写入 /nix/store/xxx-hello-1.0.0/bin/hello

函数式体现在哪里? 对于相同的输入(src 内容、依赖版本),Nix 保证输出一致。如果你想让两个人构建出完全相同的环境,只需要确保他们的 srcbuildInputs 完全相同——而这可以通过 Flakes 的 locked 版本精确控制。

2.2 懒求值:Nix 的性能核心

Nix 表达式是惰性求值的。这意味着当你写:

let
  pkgs = import <nixpkgs> {};
  vim = pkgs.vim;
  emacs = pkgs.emacs;
in
  vim  # emacs 永远不会被构建或下载

即使 vimemacs 都在 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 checknix 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 会:

  1. 启动 PostgreSQL 服务(后台)
  2. 启动 Redis 服务(后台)
  3. 配置好 Node.js、Rust、Python 环境
  4. 进入 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

维度NixDocker
粒度包级/文件级容器级
叠加能力可以叠加任意多个包版本一个镜像包含所有
可重现性内容哈希,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 developnix 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 生态有几个明显的变化:

  1. Flakes 成为事实标准nixpkgs-unstable 现在默认支持 Flakes,legacy 方式正在逐步退出
  2. Devenv 降低门槛:从写 flake.nix 到写 devenv.yaml,体验提升明显
  3. Cachix 生态完善:越来越多的团队建立自己的二进制缓存,首次构建时间大幅缩短
  4. NixOS 在服务器端增长:DigitalOcean、GitHub Codespaces 等开始支持 Nix 环境
  5. 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,你就能回到写第一行代码时的工作状态。

这,才是真正的可重现性。

推荐文章

php内置函数除法取整和取余数
2024-11-19 10:11:51 +0800 CST
MySQL 日志详解
2024-11-19 02:17:30 +0800 CST
JavaScript设计模式:观察者模式
2024-11-19 05:37:50 +0800 CST
阿里云免sdk发送短信代码
2025-01-01 12:22:14 +0800 CST
Grid布局的简洁性和高效性
2024-11-18 03:48:02 +0800 CST
Chrome DevTools MCP 深度实战
2026-06-22 20:27:14 +0800 CST
go发送邮件代码
2024-11-18 18:30:31 +0800 CST
程序员茄子在线接单