Nix Shell 开发环境

Nix Shell 提供了声明式、可复现的开发环境。本文详解 nix-shell、nix develop、flake 的 devShells、shell hooks、以及团队协作中的可复现开发环境实践。

1. 可复现开发环境的痛点

传统开发环境管理面临诸多挑战:

  • “在我机器上能跑”:不同开发者安装的开发工具版本不同,导致构建失败或行为不一致
  • 依赖版本冲突:项目需要 Python 3.9,但系统默认是 Python 3.11,全局安装会破坏其他项目
  • 新员工入职:需要数天安装和配置开发工具链,文档往往过时
  • CI/CD 漂移:本地构建通过但 CI 失败,因为 CI 环境用了不同的工具版本

Nix Shell 通过声明式配置解决了这些问题:开发环境配置以代码形式存储在项目仓库中,新成员只需运行一个命令即可获得完全一致的开发环境。

2. nix-shell 基础

2.1 临时进入含指定包的 shell

# 进入包含多个包的临时 shell
nix-shell -p python39 nodejs_20 yarn git

# 在临时环境中运行命令(不进入交互式 shell)
nix-shell -p jq --run "jq --version"

# 使用特定通道的版本
nix-shell -p python39 -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-23.11.tar.gz

2.2 shell.nix:声明式开发环境

在项目根目录创建 shell.nix,定义开发环境:

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = with pkgs; [
    python39
    nodejs_20
    yarn
    git
    postgresql_15
    redis
  ];

  # 环境变量设置
  MY_PROJECT_ROOT = toString ./.;
  
  # Shell hook:进入 shell 时执行
  shellHook = ''
    echo "========================================="
    echo "  Welcome to MyProject development shell"
    echo "========================================="
    echo ""
    python --version
    node --version
    yarn --version
    echo ""
    echo "Quick commands:"
    echo "  make dev     - Start development server"
    echo "  make test    - Run test suite"
    echo "  make db-migrate - Run database migrations"
  '';
}

2.3 进入 shell

# 在项目目录下
nix-shell

# 指定特定的 shell.nix
nix-shell /path/to/custom-shell.nix

# 使用 --pure 清除所有环境变量,只用 nix-shell 定义的
nix-shell --pure

3. mkShell 高级配置

3.1 输入依赖

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  # 构建时可用但不进入运行时环境的工具
  nativeBuildInputs = with pkgs; [
    pkg-config       # 编译时的配置工具
    cmake            # 构建系统
    autoreconfHook   # autotools
  ];

  # 运行时库和工具
  buildInputs = with pkgs; [
    openssl          # SSL 库
    zlib             # 压缩库
    libxml2          # XML 解析
    postgresql       # 数据库
  ];

  # LOCALE_ARCHIVE 修复
  LOCALE_ARCHIVE = if pkgs.stdenv.isLinux
    then "${pkgs.glibcLocales}/lib/locale/locale-archive"
    else "";
}

3.2 多阶段 shell hooks

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = with pkgs; [ python39 poetry postgresql_15 ];

  shellHook = ''
    # 如果 .venv 不存在,创建虚拟环境
    if [ ! -d .venv ]; then
      echo "Creating Python virtual environment..."
      python -m venv .venv
    fi

    # 激活虚拟环境
    source .venv/bin/activate

    # 如果 poetry.lock 存在,安装依赖
    if [ -f poetry.lock ]; then
      echo "Installing Python dependencies..."
      poetry install
    fi

    # 如果数据库不存在,初始化
    if [ ! -d $PWD/.postgres-data ]; then
      echo "Initializing PostgreSQL database..."
      initdb -D $PWD/.postgres-data
    fi

    # 启动 PostgreSQL(如果未运行)
    if ! pg_isready -h localhost -p 5432 > /dev/null 2>&1; then
      echo "Starting PostgreSQL..."
      pg_ctl -D $PWD/.postgres-data -l $PWD/.postgres-log start
    fi

    # 设置环境变量
    export DATABASE_URL="postgresql://localhost:5432/mydb"
    export REDIS_URL="redis://localhost:6379"

    echo "========================================="
    echo "  Development environment ready!"
    echo "========================================="
  '';
}

3.3 跨平台配置

{ pkgs ? import <nixpkgs> {} }:

let
  isDarwin = pkgs.stdenv.isDarwin;
  isLinux = pkgs.stdenv.isLinux;

in pkgs.mkShell {
  buildInputs = with pkgs; [
    python39
    nodejs_20
  ]
  ++ pkgs.lib.optionals isDarwin [
    # macOS 特有工具
    darwin.apple_sdk.frameworks.CoreFoundation
    darwin.apple_sdk.frameworks.Security
  ]
  ++ pkgs.lib.optionals isLinux [
    # Linux 特有工具
    inotify-tools     # 文件系统事件监听
    strace            # 系统调用追踪
    perf-tools        # 性能分析
  ];

  shellHook = ''
    ${if isDarwin then ''
      export DYLD_LIBRARY_PATH="${pkgs.openssl.out}/lib:$DYLD_LIBRARY_PATH"
    '' else ''
      export LD_LIBRARY_PATH="${pkgs.openssl.out}/lib:$LD_LIBRARY_PATH"
    ''}
  '';
}

4. Flakes 时代的 devShells

4.1 flake.nix 中的 devShell

{
  description = "My Project Development Environment";

  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
      {
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [
            # 核心工具
            go_1_21
            gopls            # Go language server
            delve            # Go debugger
            golangci-lint    # Linter
            
            # 数据库工具
            postgresql_15
            pgcli            # PostgreSQL 交互式客户端
            
            # 构建工具
            gnumake
            docker-compose
            
            # 其他
            git
            jq
            curl
          ];

          shellHook = ''
            export GOPATH=$PWD/.go
            export PATH=$GOPATH/bin:$PATH
            
            # 确保目录存在
            mkdir -p $GOPATH
            
            echo "Go development shell ready!"
            go version
          '';
        };

        # 可以定义多个 devShell
        devShells.frontend = pkgs.mkShell {
          buildInputs = with pkgs; [
            nodejs_20
            yarn
            nodePackages.typescript
            nodePackages.vite
          ];
        };
      });
}

4.2 使用 flake devShell

# 进入默认 devShell
nix develop

# 进入特定的 devShell
nix develop .#frontend

# 在不进入 shell 的情况下运行命令
nix develop --command go test ./...

# 使用另一个 flake 的 devShell
nix develop github:owner/repo

5. 团队协作最佳实践

5.1 项目模板

my-project/
├── flake.nix           # Nix Flake 定义
├── flake.lock          # 锁定依赖版本
├── shell.nix           # 兼容传统 nix-shell
├── .envrc              # direnv 自动加载
├── Makefile            # 常用命令
└── README.md           # 包含环境设置说明

5.2 shell.nix 向后兼容

# shell.nix - 兼容传统 nix-shell
(import (
  fetchTarball {
    url = "https://github.com/edolstra/flake-compat/archive/master.tar.gz";
    sha256 = "...";  # 需要填充正确哈希
  }
) { src = ./.; }).shellNix

5.3 direnv 集成

使用 direnv 可以在进入项目目录时自动加载 Nix shell:

# 安装 direnv 和 nix-direnv
nix profile install nixpkgs#direnv nixpkgs#nix-direnv
# .envrc
use flake

# 或传统方式
# use nix
# 首次进入目录时授权
direnv allow

# 之后每次进入目录会自动加载环境
# 离开时自动卸载

5.4 Makefile 集成

# Makefile
.PHONY: shell dev test lint clean

# 进入开发 shell
shell:
	nix develop

# 启动开发服务器(在 nix shell 中运行)
dev:
	nix develop --command bash -c "go run ./cmd/server"

# 运行测试(在 nix shell 中运行,确保环境一致)
test:
	nix develop --command go test ./... -v

# 运行 linter
lint:
	nix develop --command golangci-lint run

# 格式化代码
fmt:
	nix develop --command gofmt -w .

# 构建生产版本
build:
	nix build

# 清理
clean:
	rm -rf .go result

6. 高级技巧

6.1 缓存加速

对于启动慢的大型 shell,可以使用 nix-direnv 的缓存功能:

# .envrc
use flake . --impure

# 或手动缓存
if ! use flake . --impure 2>/dev/null; then
  use nix
fi

6.2 选择性加载

# 定义带参数的 shell
{ pkgs ? import <nixpkgs> {}
, withDocker ? false
, withK8s ? false
}:

pkgs.mkShell {
  buildInputs = with pkgs; [
    # 核心工具(始终安装)
    git
    vim
    jq
  ]
  ++ pkgs.lib.optionals withDocker [
    docker
    docker-compose
  ]
  ++ pkgs.lib.optionals withK8s [
    kubectl
    helm
    k9s
  ];
}
# 进入带 Docker 和 K8s 的 shell
nix-shell --arg withDocker true --arg withK8s true

6.3 与 VS Code 集成

VS Code 的 Nix Environment Selector 插件可以在打开项目时自动使用 Nix shell:

// .vscode/settings.json
{
  "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix"
}

7. 总结

Nix Shell 提供了一种优雅的方式来管理开发环境:

特性传统方式Nix Shell
环境声明README + 手动安装shell.nix / flake.nix
版本一致性依赖文档和纪律自动锁定所有版本
多版本共存困难(如 pyenv)天然支持
新员工入职数天几分钟
环境隔离虚拟环境(venv 等)自动隔离
可复现性弱(“试试 apt install xxx”)强(哈希锁定)

通过将开发环境配置纳入版本控制,团队可以保证所有人——从核心开发者到 CI 流水线——使用完全相同的工具链。这是防止"环境差异导致 bug"的最有效方法之一。

Flake Lock 与多项目隔离

lock 文件管理:flake.lock 锁定了所有依赖的精确版本(含 git commit hash)。更新依赖需显式执行:

# 更新所有输入
nix flake update

# 仅更新特定输入
nix flake lock --update-input nixpkgs

建议将 flake.lock 纳入版本控制,确保 CI 与本地环境完全一致。定期(如每周)运行 nix flake update 更新依赖并通过测试。

多项目隔离:通过 direnv 实现进入目录自动加载/离开自动卸载:

# .envrc
echo "use flake" > .envrc
direnv allow

# 效果:进入项目目录自动生效 nix shell,离开自动恢复系统环境

继续阅读

探索更多技术文章

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

全部文章 返回首页

「tools」更多文章

  1. AI 网关与模型路由:多模型统一入口、fallback、限流与成本控制
  2. 密钥与环境配置:Vercel、Cloudflare 环境变量与密钥轮换实战
  3. Web 安全加固:CSP、HSTS、安全响应头与 XSS 防护实战