使用 Nix 构建可复现的开发环境,支持绝大多数编程语言。
为什么用 Nix 管理开发环境?#
原文的语言示例固定 nixos-24.05,其中 Python 3.11、Go 1.21、Node 20 等属于版本示例,并非当下安全版本推荐。选择仍受支持的工具链并提交 flake.lock;项目包管理器的 lock、外部服务和运行状态也要一起管理。
传统开发环境的问题:
- 不同项目依赖冲突
- "在我电脑上能跑"
- 新成员入职配置耗时
- 系统升级破坏环境
Nix 解决方式:
- 每个项目独立的依赖
- 固定输入下可复用的工具环境
- 一键启动开发环境
- 与系统环境隔离
基础方式:nix-shell / nix run#
临时环境
# 进入带特定软件包的临时 shell
nix-shell -p python311 git gcc
# 运行单个命令
nix-shell -p nodejs --run "node --version"
# 使用 nix run(Flakes 方式)
nix run nixpkgs#python311 -- -c "print('Hello Nix')"shell.nix 文件
在项目根目录创建 shell.nix:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
name = "my-project-env";
buildInputs = with pkgs; [
# 构建工具
gcc
gnumake
cmake
# 语言工具
python311
nodejs_20
rustc
cargo
# 开发工具
git
vim
htop
strace
gdb
];
# 环境变量
MY_PROJECT_ROOT = builtins.toString ./.;
MY_ENV = "development";
shellHook = ''
echo "======================================"
echo " 欢迎来到 $name"
echo "======================================"
echo ""
# 显示版本
echo "工具版本:"
python3 --version
node --version
rustc --version
cargo --version
echo ""
# 设置提示符
export PS1="\[\033[01;34m\][$name]\[\033[00m\] \w \$ "
'';
}使用方法:
cd my-project
nix-shell现代方式:Flakes devShells(推荐)#
基础 Flake 开发环境
# flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "dev-shell";
packages = with pkgs; [
git
vim
direnv
];
};
};
}使用:
nix develop # 进入默认 shell
nix develop .#python-dev # 进入特定 shell各语言开发环境#
Python
基础方式
# flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "python-dev";
packages = with pkgs; [
(python311.withPackages (ps: with ps; [
# Web 框架
flask
django
fastapi uvicorn
# 数据处理
numpy pandas matplotlib
requests beautifulsoup4
# 测试与工具
pytest black mypy pylint
ipython jupyter
# 数据库
sqlalchemy psycopg2
]))
# 其他工具
poetry
pipenv
git
];
shellHook = ''
export PYTHONPATH="$(pwd)/src:$PYTHONPATH"
# withPackages 已提供 Python 依赖,不伪造 VIRTUAL_ENV
# 若要虚拟环境,需另行 python -m venv 并明确 site-packages 策略
echo "Python $(python --version) 环境已加载"
# 创建 alias
alias py='python'
alias pyserver='python -m http.server'
'';
};
};
}使用 Poetry
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
python311
poetry
git
];
shellHook = ''
# 配置 Poetry 使用项目本地虚拟环境
export POETRY_VIRTUALENVS_IN_PROJECT=true
export POETRY_NO_INTERACTION=1
# 依赖安装明确执行,不在目录进入时隐式触发网络或写文件
# 确认 poetry.lock 后,手动运行 poetry install
echo "Poetry 环境已准备"
poetry --version
'';
};
};
}Node.js / JavaScript
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "nodejs-dev";
packages = with pkgs; [
nodejs_20
corepack # npm, yarn, pnpm
# 包管理器
yarn
pnpm
# 工具
git
nodePackages.typescript
nodePackages.ts-node
nodePackages.nodemon
nodePackages.eslint
nodePackages.prettier
];
shellHook = ''
export PATH="$(pwd)/node_modules/.bin:$PATH"
# 项目安装单独执行;有 package-lock.json 时可用 npm ci
# npm install 会写入项目依赖并可能更新 lock,不在 shellHook 自动执行
echo "Node.js $(node --version)"
echo "npm $(npm --version)"
'';
};
};
}Rust
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = { self, nixpkgs, rust-overlay }:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
overlays = [ rust-overlay.overlays.default ];
};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "rust-dev";
packages = with pkgs; [
# Rust 工具链
(rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rustfmt" "clippy" ];
targets = [ "x86_64-unknown-linux-gnu" "wasm32-unknown-unknown" ];
})
# 构建工具
cargo-watch
cargo-edit
cargo-audit
cargo-tarpaulin # 覆盖率
cargo-flamegraph # 性能分析
# 系统依赖
openssl
pkg-config
gcc
# IDE 支持
rust-analyzer
lldb
];
# 环境变量
RUST_BACKTRACE = 1;
RUST_LOG = "debug";
shellHook = ''
echo "Rust $(rustc --version)"
echo "Cargo $(cargo --version)"
# 有用的 alias
alias cw='cargo watch -x run'
alias ct='cargo test'
alias cf='cargo fmt'
alias ccl='cargo clippy'
'';
};
};
}Go
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "go-dev";
packages = with pkgs; [
go_1_21
golangci-lint
gopls # LSP
gotools # goimports, gorename, etc
delve # 调试器
git
postgresql # 如果项目用到了
];
shellHook = ''
# Nix 字符串不展开 $HOME,交给 shell 设置
export GOPATH="$HOME/go"
export PATH="$GOPATH/bin:$PATH"
echo "Go $(go version)"
echo "GOPATH: $GOPATH"
'';
};
};
}Java
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "java-dev";
packages = with pkgs; [
jdk21
maven
gradle
# 工具
git
jflex
# IDE 支持
jdt-language-server
];
shellHook = ''
export JAVA_HOME=${pkgs.jdk21}
echo "Java $(java --version | head -1)"
echo "Maven $(mvn --version | head -1)"
'';
};
};
}C/C++
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "cpp-dev";
packages = with pkgs; [
# 编译器
gcc13
clang_16
llvmPackages.clang-tools # clangd, clang-format
# 构建工具
cmake
ninja
gnumake
pkg-config
# 调试与分析
gdb
lldb
valgrind
cppcheck
# 库
boost
catch2
fmt
spdlog
# 其他
git
cmake-language-server
];
shellHook = ''
export CC=gcc
export CXX=g++
echo "GCC $(gcc --version | head -1)"
echo "Clang $(clang --version | head -1)"
'';
};
};
}Haskell
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
haskellPackages = pkgs.haskellPackages.override {
overrides = hself: hsuper: {
# 可以覆盖特定包版本
};
};
in
{
devShells.${system}.default = haskellPackages.shellFor {
packages = p: [
# 你的项目包
];
withHoogle = true;
buildInputs = with haskellPackages; [
ghc
cabal-install
stack
haskell-language-server
hlint
stylish-haskell
pkgs.zlib
pkgs.git
];
};
};
}Direnv 自动加载#
安装配置
direnv 可以在进入目录时自动加载开发环境。
# /etc/nixos/configuration.nix 或 flake.nix
{ pkgs, ... }: {
programs.direnv = {
enable = true;
nix-direnv.enable = true; # 更快的加载
};
}项目配置
use flake 与 use nix 是两种替代方式,不要为同一个环境同时调用。授权前阅读 .envrc 和 shellHook,它们会执行代码。
在项目根目录创建 .envrc:
# 使用 flake
use flake
# 或使用 shell.nix(传统方式)
use nix然后授权:
direnv allow现在进入目录会自动加载环境,离开自动卸载。
数据库服务#
这类 shellHook 会创建目录并启动后台进程,退出 devShell 不会自动停止服务。把数据目录加入 .gitignore,固定数据库主要版本以避免已有数据目录不兼容;仅在本机开发使用无密码认证。
PostgreSQL
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
postgresql
pgcli
redis
];
shellHook = ''
# 设置临时 PostgreSQL 目录
export PGDATA="$PWD/.postgres"
export PGHOST="$PGDATA"
export PGDATABASE="devdb"
if [ ! -d "$PGDATA" ]; then
echo "初始化 PostgreSQL..."
initdb --auth-local=trust --auth-host=scram-sha-256 --no-locale --encoding=UTF8
# 启动 PostgreSQL
pg_ctl start -l "$PGDATA/log" -o "-k $PGDATA -c listen_addresses="
sleep 2
# 创建数据库
createdb devdb
echo "数据库已创建: $PGDATABASE"
else
pg_ctl start -l "$PGDATA/log" -o "-k $PGDATA -c listen_addresses="
fi
pgstart() { pg_ctl start -o "-k $PGDATA -c listen_addresses="; }
alias pgstop='pg_ctl stop'
alias pgstatus='pg_ctl status'
'';
};
};
}Redis
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
redis
];
shellHook = ''
export REDIS_DATA="$PWD/.redis"
mkdir -p "$REDIS_DATA"
# 配置文件
cat > $REDIS_DATA/redis.conf << EOF
bind 127.0.0.1
protected-mode yes
port 6379
dir $REDIS_DATA
daemonize yes
pidfile $REDIS_DATA/redis.pid
logfile $REDIS_DATA/redis.log
EOF
if ! redis-cli ping > /dev/null 2>&1; then
echo "启动 Redis..."
redis-server $REDIS_DATA/redis.conf
fi
alias rdc='redis-cli'
'';
};
}容器开发#
这里提供 Docker CLI,不会自动配置或启动宿主机 Docker daemon,也不会授予 socket 访问权限。容器服务应另行声明。
Docker Compose
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
docker
docker-compose
docker-buildx
];
shellHook = ''
# 检查 Docker 服务
if ! docker info > /dev/null 2>&1; then
echo "警告: Docker 服务未运行"
fi
alias dc='docker-compose'
alias dcb='docker-compose build'
alias dcup='docker-compose up -d'
alias dcdown='docker-compose down'
'';
};
}完整项目示例#
Web 全栈项目
# flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "web-fullstack";
packages = with pkgs; [
# 前端
nodejs_20
yarn
nodePackages.typescript
nodePackages.vite
# 后端 (Python)
(python311.withPackages (ps: with ps; [
fastapi
uvicorn
sqlalchemy
alembic
pytest
black
mypy
]))
poetry
# 数据库
postgresql
redis
pgcli
# 工具
git
just
jq
httpie
# IDE 支持
nodePackages.typescript-language-server
nodePackages.vscode-langservers-extracted
pyright
];
shellHook = ''
export PATH="$(pwd)/frontend/node_modules/.bin:$(pwd)/backend/.venv/bin:$PATH"
export PGDATA="$PWD/.postgres"
echo ""
echo "========================================"
echo " Web Full-Stack Development Environment"
echo "========================================"
echo ""
echo "可用命令:"
echo " just dev - 启动所有服务"
echo " just frontend - 仅启动前端"
echo " just backend - 仅启动后端"
echo " just db - 管理数据库"
echo ""
'';
};
};
}Justfile(命令运行器)
配方要求项目里的 npm scripts、Poetry 配置与 Alembic 迁移已存在。dev 是示意性的并发启动脚本,服务的退出清理和端口冲突需由项目处理。PostgreSQL 先初始化、再启动、再创建数据库。
# justfile
set dotenv-load
# 开发命令
dev:
just frontend &
just backend &
wait
frontend:
cd frontend && npm run dev
backend:
cd backend && poetry run uvicorn main:app --reload
db-start:
pg_ctl start -l "$PGDATA/log" -o "-k $PGDATA -c listen_addresses="
db-stop:
pg_ctl stop
db-init:
initdb --auth-local=trust --auth-host=scram-sha-256 --no-locale --encoding=UTF8
pg_ctl start -l "$PGDATA/log" -o "-k $PGDATA -c listen_addresses="
PGHOST="$PGDATA" createdb devdb
db-migrate:
cd backend && poetry run alembic upgrade head
# 前端命令
fe-build:
cd frontend && npm run build
fe-test:
cd frontend && npm test
fe-lint:
cd frontend && npm run lint
# 后端命令
be-test:
cd backend && poetry run pytest
be-lint:
cd backend && poetry run black .
cd backend && poetry run mypy .
be-format:
cd backend && poetry run black .最佳实践#
- 使用 Flakes:固定 Nix 输入;仍需处理外部下载与可变状态
- 配合 direnv:实现自动加载
- 文档化:在 README 中说明如何启动环境
- 版本锁定:提交 flake.lock
- 多平台支持:如果可能,支持多系统
示例完整性与实际项目
完整 Flake 示例包含 inputs、outputs、system 与 pkgs;Redis 和 Docker 片段省略了外围结构,须放入已有 outputs 作用域。haskellPackages.shellFor 的 packages 列表留空只是骨架,应填写本项目 derivation。corepack 管理 Yarn/pnpm shim,并不提供 Node 自带的 npm;根据锁定 Nixpkgs 检查包名是否仍存在。数据库示例避免 TCP 无密码监听;没有在本机执行这些服务与开发环境。
参考与延伸
根据技术笔记重新整理,示例使用通用名称。