OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 088

Nix 开发环境:多语言、数据库与全栈示例

保留 Python、Node、Rust、Go、Java、C/C++、Haskell,以及数据库和 Justfile 的完整配置。

所在专题Nix 开发环境与二进制兼容 ↗
约 28 分钟阅读开始阅读

使用 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 .

最佳实践#

  1. 使用 Flakes:固定 Nix 输入;仍需处理外部下载与可变状态
  2. 配合 direnv:实现自动加载
  3. 文档化:在 README 中说明如何启动环境
  4. 版本锁定:提交 flake.lock
  5. 多平台支持:如果可能,支持多系统

示例完整性与实际项目

完整 Flake 示例包含 inputs、outputs、system 与 pkgs;Redis 和 Docker 片段省略了外围结构,须放入已有 outputs 作用域。haskellPackages.shellFor 的 packages 列表留空只是骨架,应填写本项目 derivation。corepack 管理 Yarn/pnpm shim,并不提供 Node 自带的 npm;根据锁定 Nixpkgs 检查包名是否仍存在。数据库示例避免 TCP 无密码监听;没有在本机执行这些服务与开发环境。

参考与延伸

  1. Nix shell 环境 ↗
  2. Nixpkgs mkShell ↗
  3. Rust Overlay ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

Nix 开发环境与二进制兼容

  1. 01Nix-shell、devShell 与 direnv 的递进用法
  2. 02Nix 开发环境:多语言、数据库与全栈示例
  3. 03Python、uv 与 Matplotlib/Qt 的依赖边界
  4. 04nix-ld:预编译 ELF、加载器与库路径
  5. 05Overlays、override 与 overrideAttrs
CONTINUE READING / 下一篇Python、uv 与 Matplotlib/Qt 的依赖边界

以 Qt5Agg 和 PyPI wheel 排错为例,追踪 Python 包、系统库与插件路径。