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,离开自动恢复系统环境
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。