Hacker News new | ask | show | jobs
by nehalem 95 days ago
Although I’ve never committed to using nix system-wide, I do enjoy nix-based using https://devenv.sh/ for the very reasons described in the article. It’s much easier than local containers for development.
3 comments

I've never really understood how version pinning is meant to work with devenv.sh or Nix more generally. If I whack a .tool-versions file in my repo, everyone who works on it can use install the exact same versions of the relevant tools using asdf. That's low tech and imperfect (and certainly not a replacement for all of Nix's features), but it works as far as it goes. None of the examples on the devenv.sh page demonstrate pinning of tools/packages to specific versions.

As best I can tell, Nix enthusiasts think that this is an XY problem and that I shouldn't want to pin individual tools/packages to arbitrary versions. But the thing is that I am a rude barbarian who very much does want to do this, however philosophically misguided it might be.

If you use the flake system (which is technically still experimental, but everyone is already using it anyway), all your flake 'inputs' are automatically pinned in a flake.lock file that can be committed to git for reproducibility. So if you add nixpkgs as a flake input, your nix expressions will always be referring to the same exact package versions until you update the lock file.

The downside is that flake inputs refer to other flakes, not individual packages, so if you update the nixpkgs input it will upgrade all of your packages at once. For some packages such as Python, nixpkgs tracks multiple major versions so you can loosely pin to that version. You can also include nixpkgs as an input multiple times under different git tags/commits and only use that input for some of your packages to effectively pin them. You could keep using one nixpkgs but override the package's source to build it for a specific version/commit, but this setup could break in the future, because the derivation (and therefore build instructions) will keep evolving while your package's version will not. Or, if you really wanted to, you could straight up just copy the derivation from nixpkgs into your local repository and use that instead.

Nix is quite flexible so there's more options than just these, it just takes a little getting used to to find out what's possible. I don't use devenv myself, but some quick googling reveals it works just fine with flakes, so I would try that to see if it suits your needs.

Ok, but I guess a more concrete version of my question is the following:

> How do I set up my development environment using devenv.sh to pin nodejs to 24.14.0?

If I understand your response correctly, I can't do this in any very practical way.

Generally something like

  languages.javascript.enable = true
  languages.javascript.package = pkgs.nodejs_24
24 != 24.14.0
If you care about the _exact version you are pinning_ (even though you will guarantee the pin between team members without needing to do this step), you can either pin nixpkgs repo to the state that had that version, source the target version directly, or for some ecosystems (eg ruby) you can just specify a version with a number and it has tooling to resolve that using "traditional" approaches.

In general though when working on a team, you dont really care about the _exact semver version_, you care about the major and handle version bumps by bumping the pin of nix packages.

But that doesn’t pin to a specific version?
It does, in combination with a pinned nixpkgs commit, which you can find like this:

    ~/repos/nixpkgs$ git log --grep='nodejs.* 24.14.0' -1 origin/master
    commit 9c0e2056b3c16190aafe67e4d29e530fc1f8c7c7
    Merge: d3a4e93b79c9 0873aea2d0da
    Date:   Tue Feb 24 16:53:40 2026 +0000
    
        nodejs_24: 24.13.1 -> 24.14.0 (#493691)
    ~/repos/nixpkgs$ nix eval nixpkgs/9c0e2056b3c16190aafe67e4d29e530fc1f8c7c7#nodejs_24.version
    "24.14.0"
    ~/repos/nixpkgs$
It does, but its implicit (and so may not be the exact patch version you have in mind). See my other comment for how to handle if you _do actually care_ about pinning to a specific version explicitly.
It's one of my complaints too.

The way to do it is to find the `nixpkgs` version which contains the version of the tool you care about. There's a web site[1] that makes this pretty easy, and it's of course also doable by looking at the Git history for the program's derivation.

Then you create a named input using that nixpkgs version: either add it as a channel, import it with fetchTarball in a derivation, or add it as an input in your flake, depending on what you're doing. Then you use that named nixpkgs (or other input in the flake case) for that version of the package.

Edit: One issue with depending on things like git tags or semver versions is that sometimes people re-use versions or edit tags. Using the actual git commit hashes of the package's derivation avoids this potential ambiguity. This is why we can't have nice things.

[1] https://lazamar.co.uk/nix-versions/

Can you help me understand why devenv is needed instead of a shell like this/what is gained?

    { pkgs }:
    
    pkgs.mkShell {
      nativeBuildInputs = with pkgs; [
        # build tools
        cmake
        ninja
        gnumake
        pkg-config
      ];
    
      buildInputs = with pkgs; [
        # java
        jdk8
    
        # compilers
        gcc
        clang
        llvmPackages.libcxx
    
        # libraries
        capstone
        icu
        openssl_3
        libusb1
        libftdi
        zlib
    
        # scripting
        (python3.withPackages (ps: with ps; [
          requests
          pyelftools
        ]))
      ];
    
      # capstone headers are in include/capstone/ but blutter expects include/
      shellHook = ''
        export CPATH="${pkgs.capstone}/include/capstone:$CPATH"
        export CPLUS_INCLUDE_PATH="${pkgs.capstone}/include/capstone:$CPLUS_INCLUDE_PATH"
      '';
    }
It is a more user friendly abstraction on top of Nix. Most people don’t want or need to understand the specifics of Nix or the Nix language.

Btw, I say this as a huge fan and heavy user of both Nix and NixOS.

To be honest, I don’t know. I just enjoy the simplicity of devenv. It’s the right amount of user friendly.
“Needed” is too strong, but this does not provide services, does not provide project-specific scripts, does not setup LSP, does not setup git hooks, can't automatically dockerize your build, does not support multiple profiles (e.g. local and CI), etc.
The UX is the big benefit, especially on teams who may not even know what nix is. I held off on exposing my nix setups for a long time, but devenv has made it possible to check things in without losing a ton of time to tech support.
devenv lets you express shells as modules.

Modules let you express the system in smaller, composable, reusable parts rather than express everything in one big file. (There are other popular tools which support modules: NixOS, home-manager, flake-parts).

That devenv also provides "batteries included" modules for popular languages (including linters, LSPs) is also a benefit.

devenv also has tasks/services. For example you need to start redis, then your db, then seed it, and only then start the server. All of that could be aliases, yeah, but if you define them as aliases you can have them all up with `devenv up`. It even supports dependencies between tasks ("only run the db after migrations ran")
Hm. How it's different from home-manager?
home-manager manages your whole user's environment & desktop.

devenv does not do any user-level change (you will not be able to make it configure your WM), but works at the directory level.

For instance I'm currently working on a Rust + C++ project, and my devenv, whenever I enter this project folder: make CMake/g++/cargo/cbindgen available, enable a couple scripts to longer CMake invokations, set-up everything required for C++ and Rust LSPs, and create a couple git hooks to validate formatting etc.