Hacker News new | ask | show | jobs
by xpe 1536 days ago
> I think an easy trap with nix is the immediate desire to have it manage everything, which usually involves installing NixOS.

Can you clarity what you mean?

What do you mean by "trap"? Which (if any) of the following concepts factor into your understanding? Broken expectations? Premature optimization? Over-engineering? Lock-in?

Are you suggesting that many users of the Nix package manager "fall into a trap" and begin using NixOS?

What do surveys and/or data show about usage of just the Nix package manager? Combined with NixOS?

2 comments

Absolutely, and if it was not clear from the context, I call it a trap because that feels like what I'd fallen into at some point. Let me see if I can describe it better.

The Nix ecosystem is attractive to me at a high level because it looks like it has the possibility to revolutionize a lot of the problems that come up with an increased focus on devops culture. The problem with the Nix ecosystem itself is that it is larger and more varied than it might appear.

There are really three large entities in the ecosystem: nix the language, nix the package manager (nixpkgs), and nix the OS (NixOS). Despite the fact that there is a lot of overlap in some of the details of these three things, they are easily understood as separate entities. There are a bunch of smaller secondary entities such as nix-darwin, home-manager, devshell, and digga (formerly devos). The aim of these entities is usually to supplement, enhance, replace, or reproduce features of nixpkgs and/or NixOS.

Each of these entities has its own learning curve. It takes surprisingly little nix knowledge to use it as a dev environment generator, but nixpkgs knowledge will help a bit more. Even within nixpkgs itself, each major language has a large ecosystem built up around facilitating package building and deployment, and these are extremely heterogeneous. Some of them require that you regenerate the entire matrix of package dependencies for the package manager (javascript, haskell). Some of them have tooling that allow you to get started with almost no effort (mach-nix for python). These all have their own peculiarities. NixOS has a ton of modules, all rather heterogeneous, on top of requiring more careful knowledge of how to put together a system than most other distros. And nix the language without nixpkgs is very small, and some of what it does have built in is necessary so it can bootstrap an actual stdlib from nixpkgs.

The trap is sprung once you've been sold on the idea, and even seen it shine through some things, but feel like you have an entire wall to climb to get to a point of general competency. All of these things I've described, and even some of their subgroups, require new knowledge for a more complete understanding. I do not understand the details of many of the things I have described, and I would consider myself a power user at this point. What looks like one gigantic, insurmountable cliff is really dozens of much smaller cliffs, many of which are not necessary, at least not to most people.

At one point I wanted to test my skills in a really generic way, so I helped fix broken packages on macOS during one of the two yearly regression periods where the community tries to get the CI builds passing. I picked the packages completely at random. Most of what I ended up fixing was C/C++ based, and I don't normally work in it, or I haven't for many years, but I feel like I learned a good bit about cmake. I don't really write Haskell, but I was able to fix a bug in a solving library having to do with a missing portability shim. I didn't have help for any of this, and in some cases I was either too stuck to fix the build or unable to fix it due to problems beyond my control.

All of this is to say that I think getting to this point is a lot less painful if one takes a use case, preferably a narrow one, and focuses on learning whatever is necessary in the service of that use case. Some good topics are dev environments, using nix as an app/project builder, using nix to build containers, using nix to manage remote machines (the difference between these and personal machines is that the scope of these machines tends to be much smaller). I think people try to take on too many of these things simultaneously and end up overwhelmed. That's not to say that NixOS isn't worth learning, but I think many people could realize most or all of the real-world benefit if they use nix on an OS they're comfortable with. Coming back to my comparison of being dropped in the Romanian countryside, if you want to learn Romanian, start there. If you want to experience the countryside, maybe a guided tour is the way to go. If you want to learn about the life of Ligeti, you might not even need to learn Romanian! But total immersion is difficult and draining, and a failed immersion experience doesn't mean Romania is a bad place. So it is with Nix.

Thanks for your Nix contributions and sharing your experience here!
Many use cases can be covered just by simple uses of nixpkgs, but it's often alluring to go beyond that. e.g

package manager (whether on Darwin or any linux distro), to have some tools globally available:

    nix-env -iA nixpkgs.${some-package}
per project, most shell.nix can just look like this:

    {
      pkgs ? import <nixpkgs> {},
    }:
    let
      some_var = some_value;
    in pkgs.mkShell {
      buildInputs = [
        pkgs.some_package
        ...
      ];
    
      shellHook = ''
        # this is bash, so, whatever floats your boat
      '''
e.g asdf, only much more generic with full non-leaking package management:

    {
      pkgs ? import <nixpkgs> {},
    }:
    let
      some_package = pkgs.some_package_1_2;
    in pkgs.mkShell {
      buildInputs = [
        some_package
        pkgs.some_other_package
        ...
      ];
    
      shellHook = ''
        export SOME_VAR="some_value"
        ...
      '';
    }
ruby/rvm/rbenv/bundle exec (example for rails >= 6):

    {
      pkgs ? import <nixpkgs> {},
    }:
    let
      ruby = pkgs.ruby_2_7;
      python = pkgs.python27;
      node = pkgs.nodejs-14_x;
    in pkgs.mkShell {
      buildInputs = [
        ruby
        pkgs.sqlite
        python
        node
        pkgs.nodePackages.yarn
      ];
    
      shellHook = ''
        export RUBY_VERSION="$(ruby -e 'puts RUBY_VERSION.gsub(/\d+$/, "0")')"
        export GEM_HOME="$(pwd)/vendor/bundle/ruby/$RUBY_VERSION"
        export BUNDLE_PATH="$(pwd)/vendor/bundle"
        export PATH="$GEM_HOME/bin:$PATH"
      '';
    }
python/pyenv/venv:

    {
      pkgs ? import <nixpkgs> {},
    }:
    let
      python_packages = python-packages: [
        python-packages.pip
      ];
      python = pkgs.python38.withPackages python_packages;
    in pkgs.mkShell {
      buildInputs = [
        python
      ];
    
      shellHook = ''
        export PYTHON_VERSION="$(python -c 'import platform; import re; print(re.sub(r"\.\d+$", "", platform.python_version()))')"
        export PIP_PREFIX="$PWD/vendor/python/$PYTHON_VERSION/packages"
        export PYTHONPATH="$PIP_PREFIX/lib/python$PYTHON_VERSION/site-packages:$PYTHONPATH"
        unset SOURCE_DATE_EPOCH
        export PATH="$PIP_PREFIX/bin:$PATH"
      '';
    }
mixing arm64 and x86_64 on an Apple Silicon machine:

    {
      x86_64 ? import <nixpkgs> { localSystem = "aarch64-darwin"; },
      aarch64 ? import <nixpkgs> { localSystem = "x86_64-darwin"; }
    }:
    let
      foo = aarch64.foo;
    in aarch64.mkShell { # this makes nix-shell drop to an arm64 shell, change it to x86_64 to be intel/Rosetta2
      buildInputs = [
        foo
        x86_64.bar
        aarch64.baz
      ];
    }
using an unstable/pinned/git package:

    {
      stable ? import <nixpkgs> {},
      unstable ? import (fetchTarball http://nixos.org/channels/nixos-unstable/nixexprs.tar.xz) {},
      pinned ? import (fetchTarball https://github.com/nixos/nixpkgs/archive/ca2ba44cab47767c8127d1c8633e2b581644eb8f.tar.gz) {},
      git ? import (fetchGit { url = "https://github.com/nixos/nixpkgs/"; ref = "refs/heads/nixos-unstable"; rev = "ca2ba44cab47767c8127d1c8633e2b581644eb8f"; }) {},
    }:
    let
      foo = stable.foo;
    in stable.mkShell {
      buildInputs = [
        foo
        unstable.bar
        pinned.baz
        git.qux
      ];
    }
selecting a particular C/C++ compiler&stdlibc++ version:

    {
      pkgs ? import <nixpkgs> {},
    }:
    let
       clang = pkgs.clang_12
    in pkgs.llvmPackages_12.stdenv.mkDerivation {
      buildInputs = [
        clang
      ];
    }
The trap is: You could handle all of that by using or writing nix features. You could even use NixOS instead of whatever distro you're used to. But then by going cold-turkey you have to learn whatever nix feature on top of all the basic (as in fundamental) things nix has to offer. Purists would say "no no no this is not the sanctioned way", which is kind of true but also setting yourself up for failure; it'd be like looking at a mountain and trying to jump right to the top, which of course you will fail to, when you could just be climbing it and be successful. Whatever practical gets you on board is fine. You can get to the "pure nix" stuff later, if you ever need to.