Acme Blog

As a freelancer, I put my trust in NixOS. Here's why.

S

Sebastian Staffa

Published on Nov 7, 2024, 11:11 AM

Photo by Hide Obara on Unsplash

When I started freelancing some time ago, I got myself a Framework16 Laptop and I didn’t have to think twice about the operating system I wanted to use. At this point, I had already been using NixOS as my daily driver for more than a year and I had no hesitation building my business on that system. Today, I want to talk about the reasons why I put my trust in NixOS.

About: Nix & NixOS

First of all: This is not a tutorial. If you want to get started with the Nix ecosystem, there are many resources out there that can help you.

If you just want to understand what I am talking about, here's a short summary: Nix is a software ecosystem that includes of a functional DSL, a package manager called Nix (which can also be used standalone), and a Linux distribution called NixOS built using these two technologies. It aims to provide a declarative, reproducible way to define builds for software projects of any kind by specifying dependencies not as black boxes but as additional Nix expressions that can be (re)built at any time to create the needed binaries.

Reasons

What follows are my top three arguments for using NixOS as a freelancer. They are not about the beauty of the system or the elegance of the language, but about the practical advantages that Nix and NixOS give me in my daily project work.

Separation of Project Dependencies

As a freelancer, I work on multiple projects simultaneously for customers with vastly different project setups and languages. Especially in closed-source Python and C/C++ projects, build scripts sometimes assume that a project is the only one a developer is working on, requiring globally installed libraries (hello Boost, my old friend) and binaries.

Nix uses a concept called "Nix shells" which allows a user to launch a shell session with all environment variables (and especially the PATH variable) set so that only the specified dependencies in the specified versions are available. For all tools that run within this shell, everything appears as if installed globally. Since shell construction relies purely on environment variables, multiple shells can be open simultaneously and switched between without any problems. Additionally, all of these dependencies are immutable, or read-only, which means that they cannot be changed or updated by any process running inside the shell.

This feature can be combined with community tools like nix-direnv, which automatically sets up the Nix shell when entering a directory with a shell.nix file, or dev-env, which extends the built-in dependency management with features like process/service or git hook management to express a complete development environment in a single, declarative file.

Creating a unique shell for each project that I am working on allows me to keep my system clean while enabling scripts that assume global installations of dependencies to run without modification, which, in turn, cuts down on the time that I need to set up a new project or switch between existing ones.

Universality

Oftentimes, when I talk about Nix, people respond with a different tool they use - "X can do that too" or "Y is much easier to use." Most of the time, they're even right. The beauty of Nix is not that it can do something, but that it can do everything. As a freelancer, this is massive because it reduces the number of tools I need to master.

A recent example from my work was the Python build system Poetry, which I hadn't worked with before. Additionally, the project relied heavily on binary dependencies (since it was an ML project). Without Nix, my first task would have been to figure out how to install these binaries cleanly without polluting my system, understand how to use Poetry (likely installing it globally), and so on. But as a Nix user, I could search for nix poetry and find the poetry2nix project. With this tool, I could reframe my problem: instead of a Poetry problem, I now had a Nix problem. And Nix is something I know a bit about.

Another example is the very laptop I'm writing this on. Traditionally, I judged Linux distributions by their hardware support and how much manual setup was necessary. With NixOS, once again, a community project comes to the rescue: nixos-hardware. nixos-hardware maintains a repository of hardware fixes and configurations that can be included in your NixOS configuration. Again, Nix lets me reframe the problem: instead of searching the web for the correct udev rules for my fingerprint reader or patches to fix some Bluetooth quirks, I only need to update my NixOS configuration.

Reproducibility

The Nix build system strives to achieve bit-reproducible builds across time and space. This might sound like something only relevant for security professionals wanting to ensure that their compiler binaries are free of backdoors, but the "time" and "space" aspects have practical applications in a freelancer's daily life.

Have you ever tried to return to a project that you haven't worked on for a long time and found that it broke seemingly without reason? Maybe you ran some system library upgrades (f.e. glibc) or updated your global script interpreter (f.e. node or python) and the projects that you were working on at that time chugged along without issues. Now imagine that a client requests a small change or security fix a year or two after you last touched this client's project and you find yourself in such a situation. As a freelancer, you now have to decide whether you take the hit and fix your setup in your own time or whether you potentially anger the customer by billing for work that exceeds their expectations. With Nix, you can be confident that your dependencies haven't "rotted away". This is due to two reasons: firstly, project dependencies are separated (see above) and cannot be replaced globally while you work on a different project. Secondly, Nix has a feature called pinning, which lets users "pin" a specific state of the Nix package repository (called nixpkgs) to a specific commit. Using this pinned state, along with the ability to rebuild all dependencies from source, older dependencies can be rebuilt and used in a project, even if original download links are dead. Granted, rebuilding everything might take time, but this process can run in the background while I do other stuff, which helps me keep the cost of fixes low.

Reproducing builds across space (i.e., machines) is even more valuable, especially if this build can reproduce your entire setup, including all installed tools, dotfiles, and even fonts. This is possible with NixOS's configuration file, which can be versioned, shared across machines and even rolled back as every change to the config creates a new "generation" which can be selected during the boot process. As a freelancer, this gives me peace of mind regarding my setup: if I break my system, for example by upgrading drivers or playing around with a different window manager, I can easily roll back to a previous NixOS generation. In the worst case, if my laptop is stolen or breaks, this even enables me to set up a completely new computer to get back to work in about an hour (assuming I have new hardware at hand). I just need to install a blank NixOS on the new machine, clone/copy my configuration repository, and run a nixos-rebuild. If I have another machine with Nix, I can even create a live image from my existing configuration and use it to install on the new machine.

Downsides and the Learning Curve

First of all: Nix is a tool for nerds, and will probably stay like this for a while. It is missing any configuration GUI to speak of and the few community projects that attempted to add still have quite a way to go. This means that you have to be comfortable configuring your system (or rather: programming your system) using a text editor and the command line. Nearly every pattern that is used in other distributions won't work in NixOS because of its immutability, from configuring system services to installing fonts, one has to learn how to archive the desired outcome using Nix.

The Nix ecosystem itself doesn't come without its flaws either, the biggest one being the learning curve even for those who are determined to get into it. Firstly, it comes with its own functional programming language, which confuses many newcomers. Secondly, the ecosystem has moved quickly in recent years, and many tutorials and blog posts are outdated. Even though they still work as Nix is quite good at backwards compatibility, particularly the split between "traditional" Nix derivations introduced a lot of alternative ways to do things, which can be confusing at times(at least it has been for me). Lastly, although it has improved recently, the official documentation is still not where I go to when I need to look something up. Most of the time, for me, the best resource is the nixpkgs repository to find out how others have solved problems similar to the ones I need to solve.

Conclusion

Even with all these downsides, I am still using it. Not only that, but as a freelancer I have even built my business and ultimately my livelihood on it. It might be difficult to get into, it will repay every minute you put into it many times over when you can just run whatever you need to run, independent of space and time.

What I mean by this might become clearer when I tell you about how I set up my Framework16: I unboxed it, booted a live image, cloned my NixOS config repo, rebooted into my new old system for the first time and everything was like it had been on the machine that I had worked on for the last year.

Credits

  • Cover image by Hide Obara on Unsplash, edited by me
  • The NixOS logo used in the cover image is licensed under the Creative Commons Attribution 4.0 International License

More posts like this one

In today's post we are improving the cold start times of a Node.js Lambda function by building our own runtime image using Nix.

By Sebastian Staffa

MQTT For Web Developers

MQTT is a protocol that is typically used in an IoT context. In this article, we'll explore how we could use its capabilities in a traditional web application to stream messages in real-time.

By Sebastian Staffa

On How To Lead Devs

After I have been entrusted with leadership responsibilities, it's time to reflect on how not to mess it up.

By Sebastian Staffa