OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 030

A tutorial on Flake projects and dependency management

Work through Flake commands and project structure, from input syntax to multi-host organization.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSFlakes: project and dependency management ↗
About 8 min readStart reading

Example Versions and Verification Scope#

This tutorial retains examples using the NixOS/Home Manager 24.05 branches to illustrate structure, not to recommend deploying this historical release. Real configurations should use compatible, supported branches, retain the existing stateVersion, and check interfaces against the locked versions. Placeholder commits/hashes and ellipses must be replaced. The code has not been built or run on the target system.

This chapter covers Nix's most important advance. Flakes provide reproducible, version-locked project definitions.


What Are Flakes?#

Flakes are an experimental Nix feature that provides:

  • Reproducibility: all dependencies are locked through flake.lock
  • Composability: easily combine multiple projects
  • Standardization: a consistent input/output interface

Enabling Flakes#

NixOS Systems

# /etc/nixos/configuration.nix
{ pkgs, ... }: {
  nix.settings.experimental-features = [ "nix-command" "flakes" ];
  
  # 允许非自由软件
  nixpkgs.config.allowUnfree = true;
}

Non-NixOS Systems

# ~/.config/nix/nix.conf
experimental-features = nix-command flakes

Or enable them temporarily with command-line flags:

nix --extra-experimental-features "nix-command flakes" <命令>

Flake File Structure#

Basic Syntax

{
  # === 输入依赖 ===
  inputs = {
    # 标准格式: type:location?ref
    # type = github, gitlab, sourcehut, git, path, etc.
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    # 本地路径
    my-package.url = "path:./my-package";
    
    # Git 仓库
    my-project.url = "git+https://github.com/user/repo?ref=main";
  };

  # === 输出定义 ===
  outputs = { self, nixpkgs, ... }@inputs: {
    # packages, devShells, nixosConfigurations, etc.
  };
}

Complete Example

{
  description = "我的项目";

  # ========== 输入依赖 ==========
  inputs = {
    # nixpkgs 官方包集合
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    # 跟随 nixpkgs(使用同一版本)
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # 锁定到特定 commit
    nixpkgs-old.url = "github:NixOS/nixpkgs/a1b2c3d4";
    
    # 本地 Flake
    my-local.url = "path:/home/user/my-flake";
  };

  # ========== 输出定义 ==========
  outputs = { self, nixpkgs, home-manager, ... }@inputs:
    let
      # 定义系统平台
      supportedSystems = [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];

      # 为每个系统生成配置(自动化工具)
      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;

      # 获取对应平台的 nixpkgs
      pkgsFor = system: nixpkgs.legacyPackages.${system};
    in
    {
      # === 软件包 ===
      packages = forAllSystems (system: {
        default = (pkgsFor system).callPackage ./default.nix {};
        my-app = (pkgsFor system).callPackage ./my-app.nix {};
      });

      # === 开发环境 ===
      devShells = forAllSystems (system: {
        default = (pkgsFor system).mkShell {
          packages = with (pkgsFor system); [
            git nodejs jq
          ];
        };
      });

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

      # === Home Manager 配置 ===
      homeConfigurations.alice = home-manager.lib.homeManagerConfiguration {
        pkgs = nixpkgs.legacyPackages.x86_64-linux;
        modules = [ ./home.nix ];
        extraSpecialArgs = { inherit inputs; };
      };

      # === 格式化检查 ===
      formatter = forAllSystems (system: (pkgsFor system).alejandra);
    };
}

Inputs in Detail#

Common Input Sources

{
  inputs = {
    # GitHub(最常用)
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    # 指定分支/标签/commit
    nixpkgs-unstable.url = "github:NixOS/nixpkgs/master";
    nixpkgs-some-commit.url = "github:NixOS/nixpkgs/abc123def";
    
    # GitLab
    gitlab-repo.url = "gitlab:owner/repo/main";
    
    # SourceHut
    srht-repo.url = "sourcehut:owner/repo";
    
    # 任意 Git 仓库
    my-git.url = "git+https://example.com/repo.git?ref=main";
    my-git-ssh.url = "git+ssh://git@github.com/owner/repo";
    
    # 本地路径
    local-pkg.url = "path:./local-package";
    
    # 跟随其他输入(避免重复下载)
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      # 使用与 nixpkgs 相同的版本
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # 禁用某些输入
    some-input.inputs.unused.follows = "";
  };
}

Input Attributes

{
  inputs = {
    my-input = {
      url = "github:owner/repo";
      # 将内容当作普通源码而非 Flake;固定 commit 应写入 URL/rev
      flake = false;  # 非 flake 输入(纯内容)
      
      # 子目录
      # 使用 Flake 子目录(URL 查询参数)
      # url = "github:owner/repo?dir=subdirectory";
    };
  };
}

Flake Commands in Detail#

Basic Commands

# 查看 flake 信息
nix flake info
nix flake metadata

# 仅检查求值,不构建 checks
nix flake check --no-build
# 求值并构建 checks(不激活系统)
nix flake check

# 更新所有依赖
nix flake update

# 更新特定输入
nix flake update nixpkgs
nix flake update home-manager

# 编辑 flake
nix flake edit

# 克隆 flake 仓库
nix flake clone <flake-url>

# 显示 flake 目录
nix flake init  # 在当前目录初始化

Build Commands

# 构建默认包
nix build

# 构建特定包
nix build .#my-package
nix build .#packages.x86_64-linux.my-app

# 构建并输出路径
nix build .#my-package --out-link ./result

# 构建并打印路径
nix build .#my-package --print-out-paths

# 从其他 flake 构建
nix build github:nixos/nixpkgs#hello
nix build gitlab:owner/repo#package

Run Commands

# 运行默认包
nix run

# 运行特定包
nix run .#my-app

# 运行临时软件包(不安装)
nix run nixpkgs#htop
nix run nixpkgs#nodejs -- --version
nix run nixpkgs#cowsay -- "Hello Nix"

# 从 URL 运行
nix run github:owner/repo#script

Development Environments

# 进入开发环境
nix develop
nix develop .#default

# 进入特定 shell
nix develop .#python-dev

# 临时进入环境执行命令
nix develop --command cargo build
nix develop .#rust --command rustc --version

# nix shell 是新 CLI 的临时包环境,与 nix develop 不同
nix shell .#my-package
nix shell nixpkgs#git nixpkgs#vim

System Configuration

# NixOS
sudo nixos-rebuild switch --flake .#my-host
sudo nixos-rebuild build --flake .#my-host
sudo nixos-rebuild test --flake .#my-host

# 查看系统配置
nixos-rebuild build-vm --flake .#my-host

# Home Manager
home-manager switch --flake .#username
home-manager build --flake .#username

# Darwin (nix-darwin)
darwin-rebuild switch --flake .#my-mac

NixOS Flake Configuration#

Single-Host Configuration

# flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

  outputs = { self, nixpkgs }: {
    nixosConfigurations.my-laptop = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        {
          # 自动传递 self 作为 specialArgs
          _module.args.self = self;
        }
      ];
    };
  };
}

Multi-Host Configuration

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
  inputs.home-manager.url = "github:nix-community/home-manager/release-24.05";

  outputs = { self, nixpkgs, home-manager }:
    let
      mkSystem = { hostname, system, users }: nixpkgs.lib.nixosSystem {
        inherit system;
        modules = [
          ./hosts/${hostname}/hardware-configuration.nix
          ./hosts/${hostname}/configuration.nix
          ./modules/common
          
          home-manager.nixosModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.users = nixpkgs.lib.genAttrs users (user: 
              import ./home/${user}.nix
            );
          }
        ];
        specialArgs = { inherit self; };
      };
    in
    {
      nixosConfigurations = {
        # 笔记本
        laptop = mkSystem {
          hostname = "laptop";
          system = "x86_64-linux";
          users = [ "alice" ];
        };
        
        # 台式机
        desktop = mkSystem {
          hostname = "desktop";
          system = "x86_64-linux";
          users = [ "alice" "bob" ];
        };
        
        # 服务器
        server = mkSystem {
          hostname = "server";
          system = "x86_64-linux";
          users = [ "deploy" ];
        };
      };
    };
}

Modular Flakes#

Directory Structure

~/nixos-config/
├── flake.nix              # 入口
├── flake.lock             # 锁定文件
├── lib/                   # 自定义库函数
│   └── default.nix
├── modules/               # 可复用模块
│   ├── core/              # 核心系统配置
│   ├── desktop/           # 桌面环境
│   ├── development/       # 开发环境
│   └── services/          # 服务
├── hosts/                 # 主机特定配置
│   ├── laptop/
│   │   ├── default.nix
│   │   └── hardware-configuration.nix
│   └── server/
│       ├── default.nix
│       └── hardware-configuration.nix
├── home/                  # Home Manager 配置
│   ├── alice.nix
│   └── bob.nix
└── pkgs/                  # 自定义包
    └── default.nix

Modular Entry Point

# flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

  outputs = inputs@{ self, nixpkgs }:
    let
      inherit (nixpkgs) lib;
      
      # 系统类型
      systems = [ "x86_64-linux" "aarch64-linux" ];
      
      # 为每个系统应用函数
      forEachSystem = f: lib.genAttrs systems (sys: f nixpkgs.legacyPackages.${sys});
    in
    {
      # 导入 lib
      lib = import ./lib { inherit lib; };
      
      # 导入包
      packages = forEachSystem (pkgs: import ./pkgs { inherit pkgs; });
      
      # NixOS 配置
      nixosConfigurations = {
        laptop = lib.nixosSystem {
          system = "x86_64-linux";
          modules = [
            # 核心模块
            ./modules/core
            ./modules/desktop
            ./modules/development
            
            # 主机特定
            ./hosts/laptop
            
            # 本地包
            { nixpkgs.overlays = [ self.overlays.default ]; }
          ];
          specialArgs = { inherit self inputs; };
        };
      };
      
      # Overlay
      overlays.default = final: prev: {
        my-packages = self.packages.${final.system};
      };
    };
}

Flake.lock in Detail#

Lockfile Structure

{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1698611440,
        "narHash": "sha256-...",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "a1b2c3d4...",
        "type": "github"
      },
      "original": {
        "owner": "NixOS",
        "ref": "nixos-24.05",
        "repo": "nixpkgs",
        "type": "github"
      }
    },
    "root": {
      "inputs": {
        "nixpkgs": "nixpkgs"
      }
    }
  },
  "root": "root",
  "version": 7
}

Locking Commands

# 创建/更新锁定
nix flake lock

# 更新特定输入
nix flake update nixpkgs

# 使用特定版本
# 临时覆盖不写入锁文件;持久固定请先修改 flake.nix
nix flake metadata --override-input nixpkgs github:NixOS/nixpkgs/<rev>

# 从锁定文件恢复
nix flake metadata --reference-lock-file ./old-flake.lock

# 查看锁定信息
nix flake metadata --json

Practical Examples#

Project Templates

# flake.nix - Python 项目
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
      
      python = pkgs.python311;
      
      my-python-packages = ps: with ps; [
        requests
        flask
        pytest
        black
        mypy
      ];
    in
    {
      devShells.${system}.default = pkgs.mkShell {
        name = "python-project";
        
        packages = with pkgs; [
          (python.withPackages my-python-packages)
          git
          just  # 命令运行器
        ];
        
        shellHook = ''
          # python.withPackages 的包装器提供声明的包。
          # 不设置虚假的 VIRTUAL_ENV;如需 venv,应单独创建并激活。
          echo "Python 开发环境已加载"
          python --version
        '';
      };
      
      packages.${system}.default = pkgs.python311Packages.buildPythonApplication {
        pname = "my-app";
        version = "1.0.0";
        src = ./.;
        propagatedBuildInputs = with pkgs.python311Packages; [
          requests flask
        ];
      };
    };
}

Complete System Configuration

# flake.nix
{
  description = "NixOS 完全配置";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    nix-doom-emacs = {
      url = "github:nix-community/nix-doom-emacs";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs@{ self, nixpkgs, home-manager, ... }:
    let
      lib = nixpkgs.lib;
      
      mkSystem = { config, system ? "x86_64-linux" }: lib.nixosSystem {
        inherit system;
        modules = [
          config
          home-manager.nixosModules.home-manager
          { 
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            
            # 自动传递 inputs
            _module.args.inputs = inputs;
          }
        ];
      };
    in
    {
      nixosConfigurations = {
        laptop = mkSystem {
          config = ./hosts/laptop;
        };
        
        work = mkSystem {
          config = ./hosts/work;
        };
      };
      
      # 格式化工具
      formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.alejandra;
      
      # 模板
      templates.default = {
        path = ./templates/basic;
        description = "基础项目模板";
      };
    };
}

Frequently Asked Questions#

How Do I Inspect the Outputs a Flake Provides?

nix flake show
nix flake show github:owner/repo

How Do I Debug a Build Failure?

# 进入项目开发环境(不等于自动复现失败 derivation 的完整环境)
nix develop .#devShells.x86_64-linux.default -L

# 查看详细日志
nix build .#package --print-build-logs
nix build .#package -L

How Do I Reference a Local Path?

{
  inputs.local-pkg.url = "path:/absolute/path/to/package";
  # 或相对路径
  inputs.my-config.url = "path:./my-config";
}

How Do I Support Legacy Entry Points?

nix-build does not automatically convert a flake into a traditional expression. The project needs to provide default.nix/shell.nix; flake-compat can adapt the locked flake. Only after adding that adapter should you use:

nix-build

Best Practices#

  1. Always lock versions: use nix flake lock
  2. Separate system configurations: a dedicated directory for each host
  3. Use follows: avoid duplicate downloads
  4. Update regularly: but not blindly
  5. Back up the lock file: keep it in version control

References & further reading

  1. Nix Flake manual ↗
  2. Nix flake update ↗
  3. Nix flake check ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

Flakes: project and dependency management

  1. 01Flake structure: inputs and outputs
  2. 02flake.lock and reproducibility
  3. 03Flake Utils and project templates
  4. 04External Flake references and collaboration
  5. 05Migrating NixOS configuration to Flakes
  6. 06A tutorial on Flake projects and dependency management
  7. 07How Flake updates work
  8. 08Rolling back and manually editing flake.lock
CONTINUE READING / NEXT ARTICLEHow Flake updates work

Distinguish input updates, package sets, system generations, and cache downloads.