Six Months of Guix-ery
I've been using Guix for almost six months now. I figured it was about time for me to compile my thoughts and experiences for others. In this post, I'll provide a brief backgrounder on Guix, then talk about how I use it.
What is Guix?
Guix, pronounced like “geeks,” is two things (well, three. well, four. well... we'll get there). Firstly, it is a functional package manager inspired by Nix, pronounced like “nicks”. Secondly, it is a GNU/Linux system defined using that package manager in a strategy called “declarative system configuration.” You can run the package manager on any distribution. Historically, the Guix distro itself was called “GuixSD,” but that seems to have fallen out of favor these days. Instead, people just say “Guix” or “the Guix system.”
Now, unless you're a big nerd like me, none of that means anything to you. Assuming you know what a Linux distribution is, you should also know what a package manager is (apt, dnf, pacman, etc.), so let's start there. What is a functional package manager? Essentially, it brings the functional programming ideals of immutable state and “purity” (no side-effects; code only acts on the input you give it and produces some output that is always the same for that input) to package management. This means, in practice, that upgrades are stored in “generations,” snapshots of sets of packages after some package management operation, which can be switched between at will. It also allows for declaring packages and profiles (sets) of them much like one would data structures in a functional language, manipulating values as needed along the way. So for example, if you want to change the version of some library a program you use relies on, you can simply write a bit of code to switch out that library in its dependencies, no need to edit the package definition used by the rest of the system. Declarative system configuration extends these principles to cover the entirety of the system configuration, from base packages up. And Guix recently introduced home, which manages home directories and their so-called “dotfiles” (configuration files) as well. Nix was the first package manager designed around these ideas.
Nix and Guix
People, including myself, often hand-wave about Guix by saying it's like Nix, a much more popular, much older project which inspired Guix. In my mind, at least, this will be more likely to lead them to resources that understand and can explain better than I what functional package management and declarative system configuration are. If you're still confused, it might be worthwhile to do that; there are lots of examples of how these ideas are useful that I probably won't cover. Once you have a grasp on functional package management, or if you don't really care, we'll continue.
At its core, Guix uses Nix. Its build daemon – the background progam that puts together packages and, if necessary, compiles the programs in them – is just the Nix build daemon, written in C++. Guix also provides a functional language for package declaration in addition to its functional approach to package management, and it offers a Linux distribution and includes system configuration. All of this is also the case with Nix. I think Nix may even offer home configuration. Despite these points of crossover, however, Guix and Nix are very different beasts.
The first and most important difference to discuss is the nature of the projects. The Nix project is its own independent thing, focused on Nix. It publishes Nix itself under the LGPL 2.1 and nixpkgs package definitions under the MIT license. Guix, by contrast, is a GNU project, under that umbrella and, by extension, the umbrella of the Free Software Foundation, with all the concomitant foibles and issues that implies, including the ideological purity. In practical terms, this means Guix by default only offers free software and absolutely no binary blobs. Getting most computer hardware setups to work and downloading any nonfree software requires the Nonguix package repository, which is thankfully easy to add and provides installation images. There are a lot more implications of this that I won't discuss, but if you know you know. Suffice it to say, I do not endorse the FSF or GNU so long as their founder remains involved. And even then, I've got some notes.
Unlike Nix, Guix doesn't invent a language for users; it uses the existing GNU Guile Scheme variant. However, for package management and system configuration, Guile provides domain-specific languages, DSLs, written as extensions to Guile, to make everything nicer. As such, Guix is also a set of Guile modules (analogous to a library in other languages, with some caveats that aren't worth discussing here). Again unlike Nix, Guix itself is written in the language it uses for all of this. That is, excepting the build daemons shared with Nix, all of Guix is written in Guile. The
guix program is Guile, your home configuration (optional) is Guile, your package profile manifests are Guile, where you get your packages is defined with Guile, if you're on the Guix system even your default init system, default process 1, and system configuration are written in Guile.
This is the main technical difference between Guix and Nix, if only because the implications of it are so far-reaching, and is a big factor in my choosing Guix. Nix uses a language inspired by the ML family of functional languages, whose members include the functional heavyweights Haskell and OCaml, for its declaration langauge. However, the Nix language is its own special thing, with its own quirks and peculiarities. It is legendarily difficult to grock, especially for those unfamiliar with ML languages, and mastering it sufficiently to do all but the simplest package maintenance is considered something of a dark art. Additionally, it uses multiple languages through its stack, from C/C++ for its build daemon, to bash scripts for its builders, to the Nix language for user definitions. Guile, by contrast, is at every level, and the entirety of Guix is in one almost-pure-Guile (except patches and build scripts for Guix itself) repository.
To give an example of how this helps, I didn't know Guile before using Guix, but I knew enough of Scheme that it didn't matter. I've successfully packaged almost a dozen programs in the last months with relatively little effort (though I have run into issues I'll discuss later). I've opened up default system configuration values and messed around with their innards (a normal part of Guix usage). I've looked at source code for the Guix CLI to borrow some of its functionality elsewhere. And because Scheme is so simple and small, the relevant aspects of the language can be picked up pretty quickly compared to ML-style languages (although if you're unfamiliar with functional languages or Lisps, you may want to follow a book like How to Design Programs for a bit to get the basics down).
My Guix Journey
So let's get into the nitty-gritty. What is using Guix actually like? I'll try to give a chronological recounting of my experience, noting pain points or areas of particular niceness as they come up. First of all, though: why?
When I finally got another drive for my otherwise Windows desktop (a condition of my ownership thereof being that I cannot overwrite Windows), I decided it was my chance to try the functional package management paradigm I'd heard so much about. Up to then, I had only been using relatively low-power laptops and knew that functional package management was more resource intensive. The above discussion has probably made it fairly clear that Guix and Nix are extremely similar systems. One could just as well use Nix as Guix for most of the things that might attract a person to either distro. I chose Guix for this because I already knew Scheme and was quite confident I could quickly grock Guile. Guix has a reputation for being slow, and this holds true – though I feel it more than most because of how I do system management. If you're not on a beefy machine, it may be worth investing in learning Nix.
Installation is a fairly straightforward and simple process, if you're using a computer with libreboot, only hardware with open source drivers, and no graphics card (!). In order to get the installer booting, I had to edit the Linux commandline from GRUB to disable the kernel's efforts to load a binary module for my graphics card. I also had to tether my phone to my computer with USB to get access to a network (if I had ethernet, it'd've been fine). You can use the Nonguix ISO to avoid these issues, though I haven't personally done so.
Once you have paid penance for your heresy against Free Software (!), the installer simply walks you through a few configuration options, much like Debian, before showing you the
config.scm file it has produced for you. Approve it and it will begin the process of downloading packages, building derivatives, and installing the system.
This is gonna take a while. Guix is extremely slow for no apparent reason. I have a Ryzen 5 5600 and 16 GB of RAM, and I still make sure to upgrade when I won't need my computer for at least an hour, just to be safe, because I garbage collect regularly (it goes faster if you garbage collect less often). Installation takes even longer. So, go start reading the manual. I recommend reading it in order because otherwise you may find a page assuming you know about something you've never heard of. Linearly, though, it's great documentation, and once you get familiar with where general ideas or aspects of Guix are discussed, you can jump around no problem. You don't need to read the whole thing, but at least skimming each section and looking at its subsections is a good idea
Now that you have a base system installed (and assuming you didn't use the Nonguix ISO), you now need a functional kernel and drivers for your dirty, sinful hardware without free drivers (!). Nonguix has your back; just follow their instructions and you'll be good. Get to configuring your system!
Configuration and Daily Use
You did read that documentation, right? If not, go read at least Getting Started and Package Management. If you're on the system, you should also read at least the beginning of System Configuration and keep the rest handy for reference. Finally, I'd advise checking out Home Configuration because it's cool. It might be a good idea to break these out into separate reading periods as you're actively using what you're reading, though again going in order is advised. You'll also likely need to look at Programming Interface if you want to mess around with packages. Yes, it's a bit complex, and yes, you really do need to understand at least parts of all of the above.
For me personally, the above paragraph summarizes my first couple days with Guix. I was constantly in the documentation, making sure I was setting things up right, trying to prod and nudge without breaking things. Fortunately, if you do break things, Guix's rollback functionality means you can just revert and check what you did. And, I've had to use that relatively rarely. It's much more likely you'll get an error during an upgrade or install than that you'll get one during use.
That brings me to a major enduser painpoint. Since I've started using Guix, some package or other seems to be broken upstream and prevent me from upgrading about once a month. This isn't too big of an issue as one can simply wait a few days for the issue to be resolved – it might also help to report it in the IRC or open a bug report – or rollback Guix or use an older version of some packages for a bit. But even as I write this, a recent change in the build system leaves me unable to reconfigure my system or home at all. This means the packages they use sit stagnant and I can't change my active configuration with a new Guix release (though I could use an older version of Guix if I really needed to, something I've done before). To be fair, most other package managers would have similar issues if they were rolling releases where users pulled directly from the main branch of a git repository. However, other rolling release distros get around this by having strict commit requirements and extensive pre-commit test suites – see the Void Linux package repository for a good example. Hopefully Guix will implement such strictures in the future to reduce the number of these incidents. Reputedly, they used to be less common.
But what does it actually look like to, say, install a program?
guix install <package>. Remove it?
guix remove <package>. Roll back a generation?
guix package --roll-back. Huh, that looks a lot like every other package manager. And it is. For traditional usecases, Guix is basically the same to the end user. But underneath, it's taking advantage of all that functional goodness. Every profile has a set of files that define it in its entirety stored right in them. You can export profile manifests to send to other machines. You can create profile manifests and install them separately.
How I use Guix is much different than the simple, straightforward workflow presented above. I'll try to explain it, but just looking at my
.config/guix directory is probably better. Firstly, I use both system and home configurations. I decide what, if any, packages I want to be available to everyone who might use my computer and put them in the system configuration. I also put fonts and other system-related packages here. I put everything else in the home configuration. If there are sets of related packages in my home configuration, I may break them out into a specific profile. I use my default profile (that accessed by
guix install <pkg>,
guix upgrade, etc) for packages whose definitions I have written locally and which therefore can't be upgraded with upstream sources. In order to load in packages at login, I add a loop to my
.bashrc (see the home directory of my Guix config dir) to source all the profiles. I also prefer to completely rebuild profiles rather than simply upgrade them (so
guix package -p <profile> -m <profile-manifest.scm> instead of
guix upgrade -p <profile>) to mirror the functionality of home and system. All of this together creates a rather messy upgrade process, so I wrote a Guile script to help (there are shell equivalents in my Guix config dir).
In order to add packages, I open up the relevant configuration file, add a package, and rebuild the profile. With minor changes, this is relatively quick thanks to cached packages and configuration files and whatnot. If this also requires reconfiguring my home or system, that's only a few edits away, usually in the same file, and it takes about the same amount of time as just installing packages. I've noticed this being particularly handy for things like switching to Wayland or setting up a Pipewire home service, things that would require editing several files and installing several packages by hand on another system.
Frankly, packaging in Guix is a breeze compared to any other package definition system I've seen. It particularly shines for strict build systems with well-defined interfaces. For example, there are a variety of third-party package repositories from which
guix import can automatically generate Guix package definitions (though they sometimes need tweaking). There are some headaches, however, for less standard build systems, but even these are comparatively nice to deal with. Entire phases of the build process can be added, removed, or replaced in Guile, much like writing a lambda expression (and often using actual lambda expressions). Unfortunately, this has to be done fairly often, and there seems to be one main issue. Guix doesn't use the traditional Unix filesystem hierarchy, so shebang lines and hardcoded paths often have to be edited. There are automated steps that usually handle this without intervention, but in the body of source code or build system configurations they sometimes fail. Aside from this, most packages only face the same issues as they would in any other package manager.
I have packaged a variety of relatively small programs with little to no issue so far. Notably, my own upgrade helper; the pb tool; and qpwgraph. These are the current contents of my default profile. I also updated the driver for my wifi dongle (I simply changed the version and the hash) and did most of the work for packaging the Haxe programming language, including successfully packaging all of its dependencies (which I lost through insufficiently careful file management, entirely unrelated to Guix or any software). It's worth noting that I've also done analogous amounts of package definition on Void, packaging gdc for all supported architectures (before the PR was rejected for overcomplicating the bootstrap target, a position I agree with; I've yet to return to getting it merged separately) and updating Racket so that its 8.x releases would cross-compile (which I'm particularly proud of because it was the first time Racket 8.x was offered as binary packages for musl libc for any platform besides x86*). I say this to provide context to my comments on the packaging process. I'm quite inexperienced, but I know enough to actually do it.
There are two main bottlenecks in packaging for Guix that do not exist elsewhere. I mentioned the first above, patching file paths to handle Guix's immutable filesystem. But there's another issue created by the way Guix handles files: it's really hard to execute arbitrary programs. Now, this is overall a good thing: no one can execute a malicious binary on your computer, and since Guix is 100% open-source by default, you can theoretically manually verify every single line of code your computer ever executes. But this makes things tricky when you want to package, say, a package manager. Or, say, a game client. Like the kick-ass Minigalaxy. I never managed to get Minigalaxy packaged because I remembered it's available through the already-packaged Flatpak, but I got just far enough to realize that actually installing games was going to introduce several hurdles. The Nonguix Steam package, for example, bootstraps a temporary environment to run in. Do note, I haven't actually run into issues with this during packaging, it's just something I can see being an issue.
One thing that got me interested in finally trying out Guix was using some Python environment management tools. So naturally, one of the first things I decided to use Guix for was setting up development environments. The Guix command is
guix shell and it can be passed a list of packages. With the
-D flag, it collects development dependencies for the following package; with
-f it evaluates a file to a package; with
-m it accepts a profile manifest; and with
-p it accepts a profile directly. You'll often see configs for this in project root directories as either
guix.scm. They seem to be more or less equivalent, though by convention
guix.scm seems to be a package definition for the project which can be used to generate a development environment by
guix shell -Df while
manifest.scm gets fed to
guix shell -m.
Assuming all your dependencies are in Guix, setting up a development environments (and packaging your own programs) is pretty simple. You can even pin versions. If you need to, packaging dependencies is usually straightforward, and these definitions can be included in the config file. I did run into issues once where I needed a specific version of Python which was never packaged, an issue I never managed to overcome. Times like these are rare, however, and using a later version of Python worked fine anyway.
I've mentioned home environments a couple times, but I haven't discussed them in any depth yet. Let's do that now. If you want to setup a home configuration, you should check out the blog post on the topic. Essentially, you define your shell and its settings using a configuration much like that for system, setup any home services, import any dotfile code not handled by Guix directly, add any packages, and that's it. You can import an existing home environment to get most of this automatically. Once that's done, modifying the login environment is handled by Guix. I really like this functionality of Guix as I find manually keeping track of my dotfiles to be a bit of a hassle.
I tried getting into Julia recently and found some issues with Guix. Firstly, the Julia package manager doesn't work without p7zip being installed in the same profile. Secondly, some major Julia libraries aren't yet available in Guix (and though there is a Julia build system in Guix, there's no import handler for its packages yet). These are both issues related to the raw amount of effort that is available for Guix, and not reflections on the system itself per se. Regardless, package availability may sometimes be lackluster, especially if you're habituated to Debian-based distributions. This can cause issues since on GuixSD, it's impossible to run programs that haven't been packaged appropriately, by Guix or a Guix-installed package manager. Theoretically the ease of packaging mitigates this issue, and in practice this often holds true, but it's still annoying.
Tidbits and Goodies
One neat feature of Guix that I'm very excited about but haven't had the chance to really use is
guix pack. It allows the production of binary tarballs, Docker images, SquashFS images, or deb packages from any Guix package definition. I haven't tried it yet, and there are caveats, but it sounds promising.
You can ship off configuration files from one Guix system to another to reproduce package profiles. Combined with channel listings, which I haven't discussed, this allows arbitrarily reproducable (theoretically bit-reproducable) systems. You can also use
guix archive to share binary images back and forth.
Guix provides an immense amount of power, and that comes at a cost. I've definitely complained a fair deal about various aspects of the system. Regardless, I find the exchange to be overall worth it. It really does provide a much nicer, more understandable interface to package management. Instead of package management being something I avoid, it's something I dig into because I have learned enough of my system to be able to manipulate it the ways I want. This kind of power would not be possible on any other system without a much, much more complex set of overlapping interfaces. If any of this appeals to you, try it. You might just like it.