Nix Flakes 现代包管理

Nix Flakes 是自 Nix 2.4 引入的实验性特性,现已成为 Nix 生态系统的事实标准。本文详解 flake.nix 结构、inputs/outputs、flakes 与传统方式的对比以及 flake.lock 版本锁定机制。

1. 为什么需要 Flakes?

在 Flakes 引入之前,Nix 面临几个长期困扰:

  • 不可追踪的依赖:import <nixpkgs> 依赖于系统上配置的 nixpkgs 通道,不同机器上的通道版本可能不同
  • 版本锁定缺失:没有内置机制锁定外部依赖的确切版本
  • 命令不统一:nix-build、nix-shell、nix-env 等命令各有一套参数和行为
  • 可发现性差:社区共享的 Nix 表达方式(如 GitHub 上的表达式)难以便捷引用

Flakes 通过以下设计解决了这些问题:

  • 每个 Flake 是独立的、自包含的单元,通过 URL 唯一标识
  • flake.lock 文件锁定所有依赖的确切版本(Git 提交哈希)
  • 统一的 nix 命令行接口(nix build、nix run、nix develop、nix flake)
  • standardized 的输入(inputs)和输出(outputs)结构

2. flake.nix 的基本结构

每个 Flake 项目根目录包含一个 flake.nix 文件:

{
  # 元数据
  description = "我的 Nix Flake 项目";

  # 输入依赖(其他 flakes、Git 仓库等)
  inputs = {
    # 依赖 nixpkgs 仓库
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

    # 依赖另一个 flake(自动解析 flake.nix)
    home-manager = {
      url = "github:nix-community/home-manager";
      # 指定使用 inputs 中的 nixpkgs
      inputs.nixpkgs.follows = "nixpkgs";
    };

    # 依赖特定的 Git 提交
    my-lib = {
      url = "github:my-org/my-lib?ref=v1.2.3";
    };
  };

  # 输出定义(包、开发环境、NixOS 配置等)
  outputs = { self, nixpkgs, home-manager, my-lib }:
    let
      # 支持的系统架构
      supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
      
      # 为每个系统生成属性集的辅助函数
      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
    in
    {
      # 软件包输出
      packages = forAllSystems (system:
        let
          pkgs = nixpkgs.legacyPackages.${system};
        in
        {
          default = pkgs.callPackage ./package.nix {};
          my-tool = pkgs.callPackage ./tools/my-tool.nix {};
        });

      # 开发环境
      devShells = forAllSystems (system:
        let
          pkgs = nixpkgs.legacyPackages.${system};
        in
        {
          default = pkgs.mkShell {
            buildInputs = with pkgs; [ git nodejs_20 ];
          };
        });

      # 可执行应用
      apps = forAllSystems (system: {
        default = {
          type = "app";
          program = "${self.packages.${system}.default}/bin/my-app";
        };
      });

      # NixOS 配置
      nixosConfigurations.my-server = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./hosts/my-server/configuration.nix
          home-manager.nixosModules.home-manager
        ];
      };
    };
}

3. Inputs 详解

3.1 支持的 URL 格式

{
  inputs = {
    # GitHub(最常用)
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    
    # GitLab
    my-project.url = "gitlab:my-group/my-project/main";
    
    # 源码托管(SourceHut)
    srht-project.url = "sourcehut:~user/repo";
    
    # 任意 Git 仓库
    custom-repo.url = "git+https://example.com/repo.git?ref=branch";
    
    # 本地路径
    local-lib.url = "path:/path/to/local/lib";
    local-lib.flake = false;  # 不是 flake,只是一个裸 .nix 文件
    
    # 压缩包
    tarball.url = "https://example.com/archive.tar.gz";
  };
}

3.2 依赖覆盖(Follows)

当一个 Flake 依赖多个其他 Flakes 时,它们可能各自依赖不同版本的 nixpkgs。使用 follows 可以让它们共享同一个 nixpkgs 版本,减少重复下载和构建:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    
    home-manager = {
      url = "github:nix-community/home-manager";
      # home-manager 内部依赖 nixpkgs,但使用我们定义的版本
      inputs.nixpkgs.follows = "nixpkgs";
    };

    nix-darwin = {
      url = "github:LnL7/nix-darwin";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };
}

3.3 条件依赖

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    
    # macOS 专用依赖
    nix-darwin = {
      url = "github:LnL7/nix-darwin";
      # 只在 darwin 系统时包含
    };
  };

  outputs = { self, nixpkgs, nix-darwin, ... }:
    let
      # 通过 lib.optionals 条件使用
      darwinModules = if nix-darwin ? darwinModules 
        then [ nix-darwin.darwinModules.simple ]
        else [];
    in
    {
      # ...
    };
}

4. Outputs 详解

4.1 常见输出类型

outputs = { self, nixpkgs }:
  let
    forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" ];
  in
  {
    # 1. packages: 软件包(每个架构一个)
    packages.x86_64-linux.default = ...;
    
    # 2. apps: 可执行应用(nix run 使用)
    apps.x86_64-linux.default = { type = "app"; program = "..."; };
    
    # 3. devShells: 开发环境(nix develop 使用)
    devShells.x86_64-linux.default = pkgs.mkShell { ... };
    
    # 4. nixosConfigurations: NixOS 系统配置
    nixosConfigurations.my-host = nixpkgs.lib.nixosSystem { ... };
    
    # 5. darwinConfigurations: macOS darwin 配置
    darwinConfigurations.my-mac = nix-darwin.lib.darwinSystem { ... };
    
    # 6. homeConfigurations: Home Manager 配置
    homeConfigurations.user = home-manager.lib.homeManagerConfiguration { ... };
    
    # 7. overlays: 覆盖层,修改 nixpkgs
    overlays.default = final: prev: { ... };
    
    # 8. nixosModules / darwinModules: 可复用模块
    nixosModules.default = { config, pkgs, ... }: { ... };
    
    # 9. formatter: 代码格式化工具
    formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixpkgs-fmt;
    
    # 10. checks: 测试检查
    checks.x86_64-linux.default = ...;
  };

4.2 Flake Utils 简化

手动为每个系统写 x86_64-linux、aarch64-linux 等很繁琐。flake-utils 提供了便捷的辅助函数:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    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.callPackage ./default.nix {};
        
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [ nodejs_20 yarn ];
        };
        
        apps.default = flake-utils.lib.mkApp {
          drv = self.packages.${system}.default;
        };
      });
}

5. flake.lock 版本锁定

5.1 锁定机制

flake.lock 是 JSON 格式的锁定文件,记录了所有 inputs 的确切版本(Git commit hash):

{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1701045300,
        "narHash": "sha256-xxx=",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "abc123def456",
        "type": "github"
      },
      "original": {
        "owner": "NixOS",
        "ref": "nixos-unstable",
        "repo": "nixpkgs",
        "type": "github"
      }
    }
  },
  "root": "root",
  "version": 7
}

5.2 锁定文件操作

# 更新所有 inputs 到最新版本
nix flake update

# 更新特定 input
nix flake update nixpkgs

# 查看锁定状态
nix flake metadata

# 锁定当前状态(即使修改了 flake.nix)
nix flake lock

# 检查 lock 文件是否过期
nix flake check

5.3 分支/标签策略

{
  inputs = {
    # 跟踪最新 unstable
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    
    # 跟踪特定稳定版本
    nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    # 跟踪特定提交(适合需要绝对稳定的场景)
    nixpkgs-pinned.url = "github:NixOS/nixpkgs?rev=abc123def456";
    
    # 跟踪标签
    my-lib.url = "github:my-org/my-lib/v1.2.3";
  };
}

6. 常用 Flake 命令

# === 构建 ===
nix build                    # 构建默认包(输出到 ./result)
nix build .#mypackage       # 构建特定包
nix build github:owner/repo # 构建远程 flake 的默认包

# === 运行 ===
nix run                      # 运行默认应用(构建并执行)
nix run .#myapp             # 运行特定应用
nix run nixpkgs#htop        # 运行 nixpkgs 中的 htop(无需安装!)
nix run github:fastfetch-cli/fastfetch  # 直接运行 GitHub 上的项目

# === 开发 ===
nix develop                  # 进入 devShells.default
nix develop .#frontend      # 进入特定的 devShell
nix develop --command make  # 在 shell 中执行命令

# === Flake 管理 ===
nix flake init               # 在当前目录初始化 flake.nix
nix flake check              # 检查 flake 的合法性
nix flake info               # 显示 flake 信息
nix flake metadata           # 显示详细元数据
nix flake lock               # 更新 lock 文件
nix flake update             # 更新所有 inputs
nix flake update nixpkgs     # 更新指定 input
nix flake clone github:owner/repo  # 克隆 flake 仓库

# === 缓存 ===
nix store gc                 # 垃圾回收
nix store optimise           # 去重优化

7. Flakes 与传统 Nix 的对比

特性传统 NixNix Flakes
依赖管理通道(channels),全局状态依赖声明在 flake.nix 中,自动锁定
版本控制依赖系统上配置的 nixpkgsflake.lock 锁定所有依赖版本
可复现性弱(不同机器通道可能不同)强(lock 文件确保完全一致)
命令nix-build、nix-shell、nix-env统一的 nix build、run、develop
共享github:… 等方式引用URL 直接引用,自动发现 outputs
检查无内置检查nix flake check 验证
实验性稳定曾经是实验性,现已成为事实标准

8. Flake 模板

官方提供了一些常用模板,方便快速创建项目:

# 列出可用模板
nix flake show templates

# 从模板创建项目
nix flake init -t templates#simple
nix flake init -t templates#full
nix flake init -t templates#rust
nix flake init -t templates#go

# 使用社区模板
nix flake init -t github:serokell/templates#haskell

9. 实践建议

9.1 使用 branch + lock 策略

推荐的工作流:

  1. 在 flake.nix 中跟踪一个相对稳定的分支(如 nixos-unstable 或稳定版本)
  2. 通过 nix flake update 定期更新,测试后提交 lock 文件
  3. Ci/CD 使用 lock 文件中的版本,确保构建一致性

9.2 多架构支持

对于需要在 x86_64 和 aarch64(ARM64)上运行的项目:

{
  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in
      {
        packages.default = pkgs.callPackage ./default.nix {};
      });
}

eachDefaultSystem 会自动处理 x86_64-linux、aarch64-linux、x86_64-darwin、aarch64-darwin。

10. 总结

Flakes 从根本上改善了 Nix 的使用体验,它解决了 Nix 最大的痛点之一——依赖版本的不确定性。核心优势包括:

  • 声明式依赖:所有依赖在 flake.nix 中清晰可见
  • 自动锁定:flake.lock 确保任何时间、任何机器上构建结果一致
  • 统一的命令接口:nix build、nix run、nix develop 替代了散乱的传统命令
  • 易于共享和组合:通过 github:owner/repo 等形式便捷引用外部 flake

对于新开始学习 Nix 的用户,建议直接从 Flakes 入手,它已经替代了传统的 channel-based 工作流成为社区的主流实践。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「devops」更多文章

  1. 值班工程与告警疲劳治理
  2. 基础设施代码测试:Terratest、Kitchen 与 InSpec
  3. 发布列车与版本节奏治理