How I use neither channels nor flakes to manage my nixpkgs version

I’m kind of unclear on how novel this approach is, but it works well for me and I’m interested in what other people think of it.

I made /etc/nixos a Git repository (which I already wanted to do, tbh), and /etc/nixos/nixpkgs a submodule of it that tracks the nixos-24.05 branch of https://github.com/NixOS/nixpkgs. I used nix-channel --remove to get rid of all my channels, and instead added nixpkgs=/etc/nixos/nixpkgs to my nix.nixPath setting (though, of course, the first time you do this you’ll have to either add it before removing the channel, use nixos-rebuild switch -I nixpkgs=/etc/nixos/nixpkgs, or set NIX_PATH directly).

Now when I want to do a system upgrade, I just have to do git submodule update --remote before the nixos-rebuild switch. Switching channels is switching which branch the submodule tracks.

Additional upsides include that I can much more easily test or use my own modifications to nixpkgs, by pulling commits from my own forks, or even just modifying the files in-place if I’m iterating on something. Anything that uses uncommitted changes, or changes that will later be rebased / squashed / etc. obviously isn’t as reliably reproducible later on, but I won’t usually persist in that state indefinitely.

The main downside is just that a full clone of nixpkgs is pretty large (on disk + slow to clone + pull), and my attempts to mix submodules with shallow clones have not gone great.

Full disclosure, I never learned flakes, so I’m kind of guessing when I say that this is an alternative to using flakes for pinning your nixpkgs version?

Please let me know if this is insane / superseded by some other approach / neat / worth documenting more :slight_smile:

3 Likes

Flakes are basically just syntactic sugar to do this. You still clone the nixpkgs repo, nix just handles that for you when you use flakes. You don’t lose much performance here, basically just the eval cache.

The main benefit flakes have over this is just the additon of project structure that makes composing easier (you don’t have to just guess that importing default.nix does the right thing). It also doesn’t lock you into git - and honestly, not needing to touch submodules is nice, those can be such a PITA.

Your approach is pretty common, though, especially among those who contribute upstream. You also avoid the trap of a git checkout that doesn’t move with your config, as well as the annoying double-update you need to do with the flake equivalent if you use local nixpkgs commits.

The only reason to figure out flakes once you have this kind of setup is to go beyond importing just nixpkgs.

5 Likes

Keeping a local clone is basically what I ended up doing, but I also care about pure eval :nerd_face: so I stuck with flakes. git+file: still allows pointing at the local clone if needed, but I ended up pushing to a remote because git+file: doesn’t support as much as github: does. (And copying a git repo to the store is silly space wastage, github: downloads a tarball.)

Shallow clones are a bad idea, stick to blobless (not treeless!) clones to speed up cloning. And a git gc --aggressive can cut down on disk space further.

And I find submodules unergonomic but I get why you used it.

4 Likes

Also to really disable channels you can go a step further with nix.channel.enable = false;

Other inputs could be managed with git submodules if you wish, or with projects like npins or nvfetcher.

1 Like

If you don’t care about your own patches: GitHub - infinisil/sanix: Sane stable stateless NixOS setup. I use a variant of this (I strip out NIX_PATH from shells and just rely on nix conf to populate and + npins instead of niv). I really enjoy it. I don’t have to worry about flakes drama, but I have pleasant pinning and easy rollback, no implicit channel versioning.

There is also a blog post about the same strategy as infinisils sanix from @jade Pinning NixOS with npins, or how to kill channels forever without flakes - jade's www site