Using NixOS (As Your Main Desktop OS)

Table of Contents

NixOS is a Linux distribution that brings a functional approach to the operating system. It borrows a few ideas from functional programming: immutability, referential transparency, and declarative style, and applies them to system configuration and management of your OS.

And as it turns out, it works really well! I've been using it as my every-day operating system for the past 3 months, and it convinced me to go back to Linux after switching to OS X almost 10 years ago.

In this post I will give a quick introduction to NixOS and how it works, and also discuss my experience using it (the good and the bad).

Feel free to use the table of contents to skip around if you're not interested in a particular section.

Introduction to NixOS

NixOS is described as a "Fully Functional Linux Distribution." It uses a declarative language for specifying the system's packages and configuration. In addition, software packages, configuration files, start-up scripts, etc., are immutable. Once they are built, they are never changed.

Immutability in your operating system

Imperative programs are coded in a stateful way. You type instructions that explicitly mutate the state of your system in the course of the running program.

You can also think of typical operating systems as being imperative. For example, in most Linux distributions, upgrading a software package overwrites the files of the previous version on your system. You can think of the packages on these operating systems similar to mutable state in a program. They can change over time.

System configuration in Linux is also typically very imperative. Take the entire /etc directory for example. configuration files such as sshd_config are changed and overwritten destructively.

This mutability in your OS can cause lots of pain. There is no trace of the previous configuration and files, and the changes can ripple through your system in unexpected ways. You may have heard of DLL hell.

These are problems that NixOS attempts to fix. Packages installed through Nix, which is NixOS's package manager, are immutable. Once they are installed, they will never change.

The Nix package manager is also available for Linux, Mac OS X, and Windows. But NixOS takes it a step further and leverages Nix to build not just software packages, but the entire system. It builds the software packages, system configuration files, start-up scripts, even the kernel.

You can have the peace of mind knowing that as you upgrade packages in your system they won't unknowingly break other components. In fact, the multiple versions of the package live side-by-side in your system in complete isolation. To take a look at how this works, lets dive into the Nix Package Manager.

Nix Package Manager

Nix packages are built by writing a function called a nix expression. These functions also take a cue from functional programming languages and are referentially transparent. Their output depends only on their function arguments.

The function arguments in a nix expression take the form of source code, environment variables, and other nix expressions. If the same nix expression is evaluated with different arguments, the output will change. With nix, different output means the resulting files will be placed in a unique sub directory.

So for example if you install two versions of Ruby through Nix, the nix expression will be evaluated with the source code of the version you are installing. Because the source code of the two versions are different, the the nix expression will have two outputs and they will both placed alongside each other in the file system in different directories.

If you have multiple instances of Ruby installed on your system, NixOS will know which one to run based on the current profile.

Profiles

A nix profile is the current user environment. It is a directory that contains a collection of symbolic links to the active packages for the user. The profile is added to the search path.

In a typical Linux system, Ruby might be installed in a location such as /usr/bin/ruby. With NixOS, The active ruby will be available at <profile location>/bin/ruby. This will be a symlink pointing to the location of the active profile's Ruby binary.

This means you can have multiple profiles, each pointed to a different version of ruby on the same system, which is very nice when setting up development environments (which I will get to shortly).

Thinking in NixOS

Using NixOS changes the way you think about and use your operating system, and just like learning how to program in a different paradigm, it takes time and practice to be effective.

After a couple of months using it, I've come to think of NixOS as not only an operating system but a dependency manager for your entire system.

One of my first 'aha' moments with NixOS was trying to install Roswell, a tool similar to Ruby Version Manager, Node Version Manager, etc., but for Common Lisp. These tools offer an easy way to install and manage different versions and implementations of language interpreters and also various library packages you might install as dependencies for your software project.

There is no Roswell package available in nixpkgs (the Nix package repository), so I jumped into #NixOS on irc for help writing my first nix expression in order to build Roswell and install it on my laptop. Eventually someone asked me why I would need Roswell when any common lisp implementation I would need is already in nixpkgs.

And I came to realize that tools like Roswell, NVM, and RVM exist to give you an easy way to create isolated environments within a mutable operating system.

What else gives you isolated environments by default? NixOS!

With NixOS you can install any number of versions of the same package on your system, all of them stored in a unique directory in your file system. As mentioned above, only one of them is active at a time based on the active profile. It's simple to switch back and forth between profiles.

And it gets even easier to manage environments with nix-shell. Nix-shell is a command that builds the dependencies of a derivation and then starts a shell with all the dependencies in the shell's path. This means you can have a nix expression which work very much like a vagrantfile or dockerfile which builds an isolated development environment with all of the dependencies needed. All for much cheaper (and much simpler) since there is no virtualization or containers (or their tooling) involved.

If I need a development environment with Node.js version 4 and Redis, I can have a default.nix file on the top level of my repository that looks like:

with import <nixpkgs> {}; {
   nodeEnv = stdenv.mkDerivation {
     name = "node";
     buildInputs = [ nodejs-4_x redis ];
   };
 }

After running nix-shell in the directory where default.nix is located, I'll be dropped in a shell with both Node.js and Redis available. But they won't be available outside of that environment and won't be polluting my user environment (and maybe just as importantly, you user environment doesn't pollute your development/build environment).

My Experience

The Good

  • Development environments. I'm so happy I can get the same level of isolation without wasting the CPU and memory I spent my hard earned money on to spin up virtual machines and containers.
  • Isolated build environments. Derivations are built in a completely isolated environment so nothing on your system can leak in.
  • Since packages are created and built with a nix expression, it's really easy to take a look at the code and see all of the available options.
  • Everyone on IRC is very helpful if you have questions.
  • Did I mention the development environments?
  • Because your entire system is declared in the configuration.nix configuration file, you can wipe your laptop and do a fresh install anytime if for some reason you want to (I did this when I wanted to change my file system to btrfs).
  • It's dead simple to try out different desktop environments, window managers, or anything else, really. If you don't like it, roll back to a previous configuration with a simple command and it's like it was never there.
  • Works flawlessly on my Thinkpad X1 Carbon (2016) laptop.

The Not-quite-as-good

I have some lingering questions that I haven't answered yet. Hopefully I will find good answers to them with time.

  • So many programming languages have their own package management tool, like npm, gem, etc.. I'm not sure how they should fit into NixOS. I see there's some tools to generate nix expressions to build npm packages. But will this always be a fight between NixOS and language package managers over who will manage dependencies? (I hope NixOS wins, to be honest.)
  • How should I manage my Emacs packages? There's actually a pretty impressive list of Emacs packages in nixpkgs that I will probably end up using.
  • Changes to configuration.nix are still destructive. This isn't actually a big deal if you put it in version control somewhere, which I should probably do.
  • The installation is not the easiest. It's probably just a matter of time before it gets some nice tooling in front of it, but for now you should definitely have another computer/tablet/phone available to keep the documentation open as you make your way through it.
    • Disk partitioning needs to be done manually at this point. If you need LVM, LUKS, or anything special when it comes to your disk partitioning, you'll need to do it yourself.
  • Although I don't think I've ever had a problem, there aren't as many packages in nixpkgs as there are in some other distro's package repositories.

Conclusion

After taking some time to learn and use NixOS, I don't think I can go back to Mac OS X or any other typical Linux distro (and if I ever need to, I will use Nix as my package manager). The immutable, declarative approach to system configuration makes it dead simple to configure your system. Need LVM over LUKS on sda4 and setup initrd to decrypt the partition? Just add `boot.initrd.luks.devices = [ { name = "luksroot"; device = "/dev/sda4"; } ];` to your configuration.nix file.

Although I've spent most of my time evaluating NixOS as a desktop OS, it's also exciting to see how tools like NixOps and Disnix can take system configurations and deploy entire clusters to the cloud.

I haven't been this happy with my operating system of choice for a long time. But GuixSD sounds interesting, so I might have to give that a try next.

Author: Caleb Gossler

Last Modified: 2016-07-11 Mon 22:49