OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 082

Progressing from nix-shell to devShell and direnv

Separate temporary tools, locked project environments, and automatic directory loading into three clear steps.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSNix development environments and binary compatibility ↗
About 2 min readStart reading

️ The pain of the traditional approach#

On Arch Linux, if you install gcc, libxml2, libxslt to compile a Python project, those packages remain in the global environment until explicitly removed, and may cause version conflicts between projects. On NixOS: You can define an environment in the project directory and return to the outer shell when you exit; objects in the store remain until garbage collection removes them.

️ Stage 1: nix-shell#

Enter a temporary environment.

nix-shell -p python311 python311Packages.requests gcc
# 此时你进入了一个包含这些包的子 shell
# 退出后,这些包依然在 /nix/store,但你的 PATH 会被恢复原状

Stage 2: Flake-driven devShells#

In a modern workflow, we define the environment in the project’s flake.nix:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  outputs = { self, nixpkgs, ... }:
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in {
    devShells.x86_64-linux.default = pkgs.mkShell {
      # 运行时依赖
      buildInputs = [ pkgs.python3 ];
      # 构建时环境变量
      shellHook = ''
        echo "欢迎进入 Python 开发环境!"
        export MY_VAR="foo"
      '';
    };
  };
}

Run nix develop to enter it.

Stage 3: Automation with Direnv#

Manually running nix develop every time is tedious. With direnv, the environment loads automatically as soon as you enter the directory.

1. Enable direnv

In Home Manager:

{
  programs.direnv = {
    enable = true;
    nix-direnv.enable = true;
  };
}

2. Use it in a project

Create an .envrc in the project root:

use flake

Run direnv allow once. The next time you cd into this directory, the terminal will automatically display direnv: loading..., and all your tools and environment variables will be ready!

Why is this useful?#

  1. Version isolation: Project A uses Python 3.9 and project B uses Python 3.12, without interfering with each other.
  2. Team consistency: Share flake.nix, flake.lock, and .envrc with your colleagues, and match the target platform. The lock file pins input revisions, while running services, external downloads, and system state can still affect the actual result.

Authorization and shell integration

direnv allow authorizes the .envrc in the current directory to execute shell code, so read it first. Home Manager configuration must be paired with the appropriate shell integration. Changing directories restores environment variables; it does not automatically stop a database started by shellHook.

References & further reading

  1. Declarative shell tutorial ↗
  2. nix-direnv ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

Nix development environments and binary compatibility

  1. 01Progressing from nix-shell to devShell and direnv
  2. 02Nix development environments: languages, databases, and full-stack examples
  3. 03Dependency boundaries in Python, uv, and Matplotlib/Qt
  4. 04nix-ld: precompiled ELF binaries, loaders, and library paths
  5. 05Overlays, override, and overrideAttrs
CONTINUE READING / NEXT ARTICLEOverlays, override, and overrideAttrs

Adjust arguments and add patches to existing package definitions, then make the system actually consume the overlay.