The NixOS Tools That Actually Make a Difference

I run NixOS on three machines and I have the same set of tools on all of them. Not because I followed a guide, but because I hit the same friction points on each one and eventually fixed them the same way. This is that list.


comma — Run Software Without Installing It

comma is the one I show people first. You put a , in front of any command, and it finds the right package in nixpkgs, pulls it in temporarily, and runs it. Nothing gets installed permanently. Nothing pollutes your profile.

, cowsay "it just works", cowsay "it just works"

Under the hood it wraps nix shell -c and nix-index together. It also caches results: once you pick a derivation for a command, it remembers, and once the path is evaluated by Nix, subsequent calls are nearly instant. The benchmarks in the repo show cache level 2 is about 159x faster than cache level 1. That number is real.

It requires a nix-index database to know where files live in nixpkgs. The companion project nix-index-database provides pre-generated databases on a regular schedule, so you can skip building one yourself.


nix-index — Find What Package Owns a File

nix-index builds a local database by indexing the binary caches. Then you query it with nix-locate to find which nixpkgs package contains a specific file.

$ nix-locate 'bin/hello'
hello.out    29,488 x /nix/store/bdjyhh70...-hello-2.10/bin/hello$ nix-locate 'bin/hello'
hello.out    29,488 x /nix/store/bdjyhh70...-hello-2.10/bin/hello

The part most people actually use is the command-not-found integration. When you type a command that is not installed, your shell tells you the exact package to install instead of just printing an error. Coming from a traditional distro, this single feature makes NixOS feel less hostile.

Building the index takes around five minutes. Or skip it and use nix-index-database for a pre-built one.


nurl — Generate Nix Fetcher Calls from URLs

Writing a fetcher call by hand means figuring out the right fetcher, the right attributes, and then computing a hash by actually downloading the source. It is annoying every single time.

nurl takes a URL and an optional revision and prints a ready-to-use fetcher call with the hash filled in.

$ nurl https://github.com/nix-community/patsh v0.2.0
fetchFromGitHub {
  owner = "nix-community";
  repo = "patsh";
  tag = "v0.2.0";
  hash = "sha256-7HXJspebluQeejKYmVA7sy/F3dtU1gc4eAbKiPexMMA=";
}$ nurl https://github.com/nix-community/patsh v0.2.0
fetchFromGitHub {
  owner = "nix-community";
  repo = "patsh";
  tag = "v0.2.0";
  hash = "sha256-7HXJspebluQeejKYmVA7sy/F3dtU1gc4eAbKiPexMMA=";
}

It supports fetchFromGitHub, fetchFromGitLab, fetchFromGitea, fetchFromSourcehut, fetchCrate, fetchPypi, fetchhg, fetchsvn, and more. It avoids slow fixed-output derivations when faster alternatives exist. And it is the foundation that nix-init builds on.


nix-init — Generate Full Nix Packages from URLs

If nurl handles the fetching, nix-init handles everything else. Point it at a URL and it generates a complete package expression with hash prefetching, dependency inference, and license detection. All through interactive prompts with fuzzy tab completion.

Supported builders: stdenv.mkDerivation, buildRustPackage, buildPythonApplication, buildPythonPackage, and buildGoModule. For Rust projects it infers the cargo hash automatically. For Python it picks up dependencies from PyPI metadata.

The docs are honest about this: the output will probably need tweaks, and you should verify the license and description yourself. But it does the 80% that nobody wants to do manually, which is why packaging contributions happen at all.


nh — A Better CLI for NixOS, Home Manager, and Darwin

nh reimplements nixos-rebuild, home-manager switch, and darwin-rebuild with better output and a few genuinely useful additions.

PlatformWithout nhWith nh
NixOSnixos-rebuild switch --flake .#myHostnh os switch . -H myHost
Darwindarwin-rebuild switch --flake .#myHostnh darwin switch . -H myHost
Home Managerhome-manager switch --flake .#myHostnh home switch . -c myHome

The shorter syntax is nice but not the point. When you run nh os switch, you get a build tree from nix-output-monitor, a diff of what is actually changing in your derivations, and a confirmation prompt before anything gets activated. You stop switching blind.

nh clean extends nix-collect-garbage with gcroot cleanup, profile targeting, and time-based retention like --keep-since 4d. A NixOS module in nixpkgs can wire this up as an automatic service.


statix — Lint Your Nix Code

statix checks Nix files for antipatterns and fixes the ones it can.

$ statix check tests/c.nix
[W04] Warning: Assignment instead of inherit from
   ╭─[tests/c.nix:2:3]

 2   mtl = pkgs.haskellPackages.mtl;
   ·   ─────────── This assignment is better written with inherit$ statix check tests/c.nix
[W04] Warning: Assignment instead of inherit from
   ╭─[tests/c.nix:2:3]

 2   mtl = pkgs.haskellPackages.mtl;
   ·   ─────────── This assignment is better written with inherit
$ statix fix --dry-run tests/c.nix
-  mtl = pkgs.haskellPackages.mtl;
+  inherit (pkgs.haskellPackages) mtl;$ statix fix --dry-run tests/c.nix
-  mtl = pkgs.haskellPackages.mtl;
+  inherit (pkgs.haskellPackages) mtl;

It catches redundant pattern bindings, collapsible let...in blocks, manual inherit patterns, unnecessary boolean comparisons, unquoted URIs, and more. The full list is available via statix list. Specific lints can be disabled in a statix.toml at the project root, and it respects .gitignore by default.

Plug it into your editor and forget about it.


nix-direnv — Fast, Persistent Dev Shells

nix-direnv replaces direnv's built-in use_nix and use_flake. The built-in version re-evaluates on every shell entry, which gets slow. nix-direnv caches the result and only rebuilds when your shell.nix or flake.nix actually changes.

The part I care about more: it pins the shell derivation in your Nix gcroots. Garbage collection will not delete your build dependencies. I lost hours of cached downloads to a nix-collect-garbage run before I started using this.

Setup with Home Manager:

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

Add use flake or use nix to .envrc, run direnv allow, and your environment loads automatically on cd. Works with classic shell.nix and flake-based devShells.


flake-parts — The Module System for Your flake.nix

Every non-trivial flake.nix eventually becomes a mess. Packages for multiple systems, devShells, CI checks, a NixOS module. You end up with 300 lines of forEachSystem repetition and nobody wants to touch the file.

flake-parts brings the NixOS module system to flakes. You split the configuration into focused files and flake-parts assembles them. It handles the system boilerplate and makes it easier for others to pull your flake's outputs into theirs.

Migration is gradual. Wrap your existing outputs in mkFlake and move things over piece by piece:

outputs = inputs@{ flake-parts, ... }:
  flake-parts.lib.mkFlake { inherit inputs; } {
    systems = [ "x86_64-linux" "aarch64-linux" ];
    # perSystem and other modules go here
  };outputs = inputs@{ flake-parts, ... }:
  flake-parts.lib.mkFlake { inherit inputs; } {
    systems = [ "x86_64-linux" "aarch64-linux" ];
    # perSystem and other modules go here
  };

Projects using it include nixd, argo-workflows, and hyperswitch. It has become a standard approach for flakes that need to stay maintainable.


hjem — Home File Management Without the Overhead

If you have ever wanted Home Manager's home.file without the rest of Home Manager, hjem is worth knowing about.

Hjem ("home" in Danish) is a NixOS module that manages files in $HOME. That is mostly it. No program modules, no environment activation scripts, no opinion about your shell. You declare files and it links them into place using smfh, an atomic file linker backed by systemd services.

hjem = {
  users.k1ng = {
    enable = true;
    files = {
      ".foo".text = "bar";
      ".config/something.json" = {
        generator = lib.generators.toJSON { };
        value = { foo = 1; bar = "hello"; };
      };
    };
  };
};hjem = {
  users.k1ng = {
    enable = true;
    files = {
      ".foo".text = "bar";
      ".config/something.json" = {
        generator = lib.generators.toJSON { };
        value = { foo = 1; bar = "hello"; };
      };
    };
  };
};

It is multi-user by design. Each key under hjem.users maps to a user in users.users, and hjem refuses to manage homes for users that do not exist.

The honest comparison to Home Manager: hjem does far less. It does not abstract away program configuration. There is no programs.git.enable equivalent. The project is explicit that application-specific modules are out of scope, though a separate project called hjem-rum is building that layer on top.

I think that constraint is actually the point. Home Manager is large, occasionally opinionated in ways you did not ask for, and has a reputation for activation scripts that sometimes conflict with NixOS's own. Hjem is small enough that you can read the entire codebase in an afternoon. If all you want is declarative dotfile management wired into your NixOS config without pulling in a second module framework, hjem is the cleaner answer. It is still young at around 330 stars, so I would not call it battle-tested, but it is actively maintained and the scope is intentionally narrow enough that "mostly feature-complete" is a realistic claim.


NixOS Options Search — Keep This Tab Open

Not something you install: search.nixos.org/options.

It searches the entire set of NixOS module options. Type, default value, which module declares it. When you cannot remember if it is services.nginx.enable or programs.nginx.enable, this is where you go. It is faster than reading source, and honestly faster than grepping nixpkgs.

It also covers Home Manager options and has a package search tab. I have it open on every machine I work on.


None of these are required. NixOS runs fine without them. But each one fixes something that is genuinely annoying without it, and after a while you stop noticing the friction because you stopped having it.

FAQ

Q Do I need all nine tools to get started with NixOS?
A No. If I had to pick one, it would be comma. It removes the friction of trying software without committing to an install. Add nix-index for the command-not-found integration and you already have a noticeably better experience.
Q Does nix-direnv replace lorri?
A It is a direct alternative. nix-direnv requires no background daemon and avoids the CPU spike lorri causes when it re-evaluates all of nixpkgs on every change.
Q Will nix-init generate a package that builds out of the box?
A Probably not. The project itself says the output will need tweaks. But it handles the tedious parts: hash prefetching, dependency inference, license detection, and builder selection. You fix the remaining 20%.
Q Is flake-parts worth it for a small personal flake?
A Probably overkill for a single-machine NixOS config. It pays off once you have multiple packages, devShells, and CI checks across several systems. That is where the module structure starts to matter.
Q What is the difference between nurl and nix-init?
A nurl generates just the fetcher call, the fetchFromGitHub block with the correct hash. nix-init builds on top of nurl and generates the entire package expression including builder, dependencies, and metadata. Use nurl when you need a hash. Use nix-init when you are packaging something from scratch.
Q Does comma work offline?
A It depends on whether the package is already in your local Nix store. With cache level 2 enabled, previously used commands run from the cached path without any network access. If the package has never been fetched, comma needs to download it.
Q Is hjem a replacement for Home Manager?
A No, and it does not try to be. Hjem handles file linking into $HOME. It has no program modules and no opinion about your packages. If you want a full Home Manager-style experience on top of hjem, look at hjem-rum, which builds that abstraction layer separately.
About the Author

Asaduzzaman Pavel

Software Engineer who actually enjoys the friction of well-architected systems. 15+ years building high-performance backends and infrastructure that handles real-world chaos at scale.

Open to new opportunities

Comments

  • Sign in with GitHub to comment
  • Keep it respectful and on-topic
  • No spam or self-promotion