tazjin's blog


Trying Guix: A Nixer's Impressions

Note: This post is a draft! Please do not share the link to it without asking first.


People occasionally ask me how I think Guix compares to Nix. Let me set the stage: I've been using Nix for many years, have large projects using Nix, used to be very active in the Nix community, and even wrote multiple Nix language interpreters, so I'd say that I'm at least fairly comfortable with Nix.

I'm also one of those people who live in Emacs. I'm no stranger to Lisps (although my experience with Scheme is limited) and am very fond of them. It feels natural that people ask me about my views on Guix.

The thing is: I haven't actually ever used Guix, so I just don't know. But there's an easy way to find out: let's try it! So that's what I did this weekend.

Here are a few things I ran into and found noteworthy. My goal was to take my Unchartevice laptop with its strange Zhaoxin x86_64-compatible CPU and see if I could get all the way to my standard niri desktop. There's no overarching point here, just observations a user of one or the other system might find interesting.

Spoiler: I didn't manage, but I saw a lot of interesting stuff. Knowing myself, I'll probably keep trying when some free time shows up.

A quick note before we begin: Technically, Guix uses a fork of the Nix daemon for the low-level primitives of functional package management (the Nix Guix Store, derivations, substitutions, and all that stuff are basically the same). This fork occurred a long time ago, and development has diverged significantly since then. The similarities end here, however: Guix is not "just" Nix with Scheme—it's a complete alternative ecosystem built in parallel on top of the same foundational infrastructure. A mental model of "Nix with Lisp syntax" will not work in practice when approaching Guix.

Anyway, here we go.

nonguix

The very first thing I ran into is more political, but I must mention it, as almost everyone trying the Guix System will face this immediately. Skip this if you want to get straight into the technical stuff.

Guix is the GNU system, and as such takes software freedoms very seriously. Guix does not recommend and does not ship the proprietary blobs needed for most modern hardware. The FSF has a website that lists laptops that work without them, but it is very limited. The majority of Guix System users use something called nonguix that adds these blobs, giving you access to things such as wireless internet.

I don't want to make this post about the political side of this, but I had to use nonguix to get internet working on the machine, which had very immediate technical effects that bring me to:

Architectural Differences

A major and immediate difference between Guix and Nix is that they layer things differently. Nix works like this:

[ nix-daemon ] <-> [ Nix CLI ] <- [ Nix code ]

The brackets here are intended to signify independence: You can mix (to some extent) separately built nix-daemons and Nix CLIs, and you can use almost any version of Nix from the last 8 years or so to evaluate almost any Nix code (let's, please, not get into why flakes are nonsense why this is only "almost" true).

The Nix CLI knows nothing1 about nixpkgs. In most Nix code, something, somewhere will import a commit of nixpkgs, which yields an enormous data structure lazily containing all Nix packages, and then use bits of this structure. Importing multiple nixpkgs commits is no problem—it just means that you have two large data structures now. In effect, this means that you can mix and match nixpkgs commits (or other Nix config) freely within the language, and Nix always evaluates the entire thing.

In fact, in TVL we do this all the time, because we track unstable releases and occasionally need to pick software from an older stable commit.

Guix doesn't work like this, and I found it very confusing at first. That's not to say it's bad—it's just different:

[ guix-daemon ] <-> [ guix CLI + profile ] <- [ Guix user code ]

As in Nix, the Guix daemon converses with the CLI over an RPC interface. The difference is that the Guix CLI runs in a fixed profile that has all of the packages and modules from all channels baked in. In contrast to Nix, the Guix package/service set is not one big data structure, but a namespaced hierarchy of Scheme modules. The Guix CLI is a Scheme environment in which all of these things are available for import in user code.

This means that to change the Guix version (i.e., commit in their monorepo, which is the implementation of the CLI and of the package set, as if NixOS/nix and NixOS/nixpkgs were one) you essentially rebuild Guix. In Guix land, this happens through a command called guix pull, which ultimately yields (as I understand it) a new guix binary with a hardcoded profile.

This has two effects that were noticeable to me:

  1. Switching between versions is always at least a two-step process: Rebuild Guix, then rebuild your config. You can't easily2, as in Nix, just import a different Guix commit in your code.
  2. Running guix pull is slow, and this makes the initial bootstrapping experience very frustrating. It's super-easy to build a configuration from some Guix commit, then run the wrong command and cache-bust everything as the commit changed (which, due to nonguix, often leads to a full Linux kernel rebuild—something you do not want to do on a Zhaoxin KX-6640MA!).

There isn't a right-or-wrong here: Guix uses a different model from Nix. For me, the Nix one feels more natural, but this might just be bias due to familiarity.

For what it's worth, Liam from the #guix IRC channel pointed me towards a method for abstracting this away, but I haven't tried it out yet.

Another thing that seems architecturally different is profile-wide installs of packages. In Guix they seem to be preferred over the Nix approach of creating isolated environments for specific programs with helpers. The most noticeable one for me was Emacs: People usually install Emacs packages right into their system or user profile from which Emacs loads them, whereas Nix has emacs.withPackages that takes a list of packages and builds a full Emacs with these packages baked in.

I haven't figured out if there is an equivalent to Nix's emacs.withPackages. Maybe I didn't look at the right people's configs? If there isn't, this kind of design makes some experimentation harder than on Nix.

Documentation & Onboarding

Guix's community has a much more focused culture than Nix, which is currently a bit of a mess of different corporate interests pulling in different directions with little ideology and direction.

This has the effect that Guix can get some stuff done that I think would be difficult to organize in Nix. An area where this is noticeable is documentation: Guix's is many times better than Nix's. Things are structured logically, available in info, all the Scheme constructs needed are documented like any other Scheme code, and so on.

The thing is, I'm not actually sure if this helps smooth the onboarding in any way because you have to already know Scheme, which is a more complex language than Nix. On the other hand, the skill of knowing Scheme translates to other domains, so you might argue that it's an investment that pays off more.

Another thing that complicates onboarding is the whole nonguix situation: There are no recent ISO images for installers with unfree firmware, and there's only a handful of posts strewn across the internet that will help you get up-and-running.

Maybe the documentation in both cases (Nix and Guix) isn't really going to help beginners, but it helps confident users more effectively use the system. Guix does this better.

Performance

Guix is noticeably slower than Nix. We complain about NixOS evaluation with C++ Nix taking a long time, but Guix feels an order of magnitude slower.

On this laptop, a guix pull (remember, this is the equivalent to updating your nixpkgs pin on Nix) can easily take 30-50 minutes. After that you still need to evaluate the system config, check for substitutes, build it, and so on. Sure, this is a laptop with a CPU broadly equivalent to old Intel Atom CPUs, but on this same machine Nix performs much better (evaluating and switching to a new system config in 5-10 minutes).

I read that this gets better once the system is stable, and commits don't keep changing much and so on, but I haven't reached that state yet, and getting there is hard.

Due to the kernel rebuilds I ended up installing Guix on nevsky, the powerful TVL build server, and building the system config there (the Guix package manager runs well on NixOS, and vice-versa). I couldn't figure out an easy way to get the system closure from there to the laptop though, as guix copy --from=... doesn't seem to work for HTTP substitution. It seems like evaluating the config locally is unavoidable.

I'm curious why this is: The Guile interpreter has a JIT, and Guix has a more imperative evaluation model which should (unintuitively) be able to avoid some of the work happening in Nix's magic recursive fixpoint sets. Maybe there are some low-hanging fruits and this just hasn't been a priority? I don't know, but I'd be interested in finding out.

Of course, a CPU with these performance characteristics is an outlier in 2025, but it makes the difference more noticeable.

Shepherd vs. systemd

The Guix system does not use systemd. This is great—I've ranted before (in Russian) about how much I dislike the current state of systemd, and there are much more detailed posts about why systemd (albeit being an improvement over what came before it) is not very good.

Guix instead uses Shepherd, an init system written in Scheme. I don't have much to say about it yet, but it seems fairly straightforward and has excellent documentation. Once I continue with my experiment, I'll take a look again.

Conclusion

Where I ended up after hacking on this for the weekend:

I've got Guix running on the laptop, however without a graphical UI. Some hardware configuration bits are missing, and as there doesn't seem to be an equivalent to nixos-generate-config I still have to invest some time in guessing which bits. I failed to configure the same channels that I used during installation on the machine itself, so now I have to go through at least one more extremely slow guix pull cycle to evaluate and be able to substitute the next config generation, which is already built on nevsky. Feels like I'm doing something wrong, but that is learning.

Despite the problems I ran into, Guix is still intriguing: Lisp is a big plus, and the Guix ecosystem feels a lot more coherent than Nix. Would Guix be able to give me anything that Nix doesn't? I don't know. My first milestone would be just getting something equivalent to my NixOS desktop config running there, and figuring out a quicker way to iterate. The rest comes later.

  1. If you send me a comment saying that technically the Nix CLI knows about the magic nixpkgs channel syntax in NIX_PATH entries I will force you to run Ubuntu in production.

  2. Yes, I know about Guix inferiors.