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 的对比
| 特性 | 传统 Nix | Nix Flakes |
|---|---|---|
| 依赖管理 | 通道(channels),全局状态 | 依赖声明在 flake.nix 中,自动锁定 |
| 版本控制 | 依赖系统上配置的 nixpkgs | flake.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 策略
推荐的工作流:
- 在
flake.nix中跟踪一个相对稳定的分支(如nixos-unstable或稳定版本) - 通过
nix flake update定期更新,测试后提交 lock 文件 - 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 工作流成为社区的主流实践。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。