LWN.net Logo

NixOS: purely functional system configuration management

June 17, 2009

This article was contributed by Koen Vervloesem

System configuration management is a notoriously difficult task. Upgrading packages, editing configuration files, and so on; there will always come a time that it goes wrong. To mitigate this problem, Eelco Dolstra of Delft University of Technology invented another approach. He implemented a purely functional package manager called Nix, which means "nothing" in Dutch. Dolstra began his work on the Nix package manager as a part of his PhD research [PDF] at Utrecht University:

We ran the Nix package manager on existing Linux distributions, such as SUSE and Red Hat, parallel to the native package manager of these systems. Later we implemented a Linux distribution called NixOS on top of it to see if we could manage not only software but a complete system configuration in a functional way. NixOS was the ultimate empirical validation of a purely functional approach.

This operating system NixOS was the work of Armijn Hemel, who wrote a prototype for his Master's thesis. After this success, other developers joined. There are no official statistics of the number of users, but according to Dolstra the latest release of the Nix Packages collection had 19 contributors.

Imperative versus functional systems

To be able to grasp the basics of NixOS, we first have to distinguish between imperative systems and functional systems. Traditionally, software packages and configuration data (/bin and /etc, respectively) are imperative data structures. System administrators update them in-place with various administration commands, e.g. a RPM or APT package manager or a configuration tool such as Cfengine. This is analogous to how imperative programming languages such as C work. Each configuration action is stateful: it depends on the current state of the system and transforms this state. This has some fundamental consequences, including:

  • No traceability: a specific configuration can generally not be recreated from scratch on a pristine system. That is, there may not be a record of the sequence of configuration actions over time. So it's not easy to reproduce a configuration.
  • No predictability: if a configuration action acts upon an ill-defined state, the end result may be equally ill-defined and thus unpredictable.
  • No rollbacks: if the user upgrades his system configuration (e.g. by upgrading a set of packages), this is a destructive process and undoing this is hard. Possible solutions are reverting to a backup or installing previous versions of the packages, but both solutions are error-prone.

In contrast, NixOS uses a functional approach, analogous to functional programming languages like Haskell. As Dolstra and Hemel state in their paper Purely Functional System Configuration Management [PDF]:

In this approach, the static parts of a configuration —software packages, configuration files, control scripts— are built from pure functions, i.e., the results depend solely on the specified inputs of the function and are immutable. As a result, realising a system configuration becomes deterministic and reproducible. Upgrading to a new configuration is mostly atomic and doesn't overwrite anything of the old configuration, thus enabling rollbacks.

The functional approach has several advantages:

  • Traceability: a configuration can be realised deterministically from a formal description and reproduced easily on another machine.
  • Predictability: realisations of a configuration are not stateful, and hence upgrading a configuration is as safe as installing from scratch.
  • Rollbacks: configuration changes are not destructive. As a result, the user is always able to roll back to a previous configuration.

How does NixOS work?

All this sounds exciting, but is it more than an academic exercise? How does NixOS work in practice? To put this to the test, your author downloaded the latest ISO of the installation CD. This CD contains a basic NixOS installation and doesn't do any installation preparation. So the user has to partition and format the drives himself and mount it on the target file system. The installation procedure is explained in an online manual.

The installation itself is uncommon, already showing signs of the functional nature of the operating system. The user has to write a description of the configuration to a file on the target file system. This file contains a 'Nix expression' that defines the root file system, kernel modules and services. Fortunately, the user can generate an initial configuration with the command nixos-hardware-scan. The nixos-install command reads this file and installs the system.

The result is a bare bones Linux distribution with the Nix package management system and the Upstart init system. The Nix package collection contains about 2200 software packages. That makes it a rather small distribution, but it is usable, as it contains server software such as Apache and SSH, and desktop software such as X.org 7.4, KDE 4.2, parts of Gnome, Firefox and more. However, the curious user will soon find other signs of the strangeness of this distribution. In the filesystem layout, for example: there's no /sbin, /usr/ or /lib in the filesystem. There's only one symlink in /bin, /bin/sh, because Glibc's system() function hard codes the location of the shell, as many other programs do. Even most files in /etc are symlinks.

All this is by design: to be able to work in a purely functional way, all static parts of the NixOS operating system are stored as immutable files in directories under /nix/store. Each package has a 'Nix expression', which is a function that builds and installs this package from source. The build scripts store the built packages in the Nix store. Each package is stored in a directory with a name that begins with a 160-bit cryptographic hash of all inputs involved in building the package, for example 22bharrqlcisnwa11a5qr0xazgvv64hk-firefox-3.5b4. This means that any change to an input value causes the package to be rebuilt in a different path, which has as a side effect that previous versions of the package are left untouched. Input values include the sources of the package, the build script, any arguments or environment variables passed to the build script, and build time dependencies. Each package directory contains bin, lib, man, and other sub-directories for the package and is read-only.

The same scheme works for system configuration files and control scripts. So the system has Nix expressions for sshd_config, to build the Linux kernel, to build initrd, for boot scripts, etc. There's also a top-level Nix expression, system.nix, that builds the entire system configuration by calling all expressions. The output is an activation script that can be executed to make this configuration the current configuration of the system. For example, it modifies the GRUB boot menu to boot the system with the new configuration. Previous configurations are retained in the boot menu to roll back. The nixos-rebuild switch command builds system.nix, makes it the default configuration in the GRUB boot menu and calls the activation script.

By not storing components such as libraries, header files or programs in global locations, all packages are forced at build time to use a specific version of their dependencies, located in the Nix store. To make this happen, the developers have patched Glibc, GCC and the dynamic linker ld to not search files in any default locations. So if a dynamic library is not explicitly declared with its full path in an executable, the dynamic linker will not find it. This also means that if the developer fails to specify a dependency explicitly in the Nix expression language, the package will fail deterministically, even if the dependency already happens to be available in the Nix store.

Advantages and disadvantages

Fortunately the user doesn't have to know about the Nix store. The user doesn't have to type /nix/store/22bharrqlcisnwa11a5qr0xazgvv64hk-firefox-3.5b4/bin/firefox to start their favorite browser. Nix creates directory trees of symlinks to all activated components and calls them user environments, which also reside in the Nix store. After each Nix package action, such as an install or a rollback, a new generation is made, which is a symlink outside the store that points to a user environment in the store. All generations of a user are grouped together in a profile. The user's current profile is pointed to by the symlink ~/.nix-profile. Putting the directory ~/.nix-profile/bin in the user's PATH environment variable completes the picture. As a consequence, non-privileged users can also securely install software. If a user installs a package that another user has already installed previously, the package won't be built or downloaded a second time.

Apart from the obvious advantages of predictability and rollbacks, it's even possible to copy a package from one machine to another in NixOS. The command nix-copy-closure copies a Nix store path along with all its dependencies to or from another machine via the ssh protocol. It does this efficiently, because it doesn't copy store paths that are already present on the target machine. This makes Linux packages literally "portable".

One major drawback to the functional model is that it requires significant disk space. This is understandable as each time the user makes a change to his configuration a new package will be added without overwriting the older one. In the worst case disk space doubles, for example when the C library or compiler is changed, propagating to all other packages. Even if the user removes a component, Nix doesn't actually remove the component from the Nix store, because it might still be in use by another user's environment or be a dependency of another component. Moreover, if the component were removed, it would no longer be possible to perform a rollback. However, the user can always remove any old generations of his profile by nix-env --remove-generations old and then he can execute a garbage collection of all unneeded components with nix-store --gc. If your hardware is not supported out-of-the box, it can be a challenge to write the correct Nix expression to load the firmware. There's also no distinction between a stable and an unstable branch, so things tend to break now and then. Fortunately, a broken system can be rolled back easily.

One final apparent drawback is that the NixOS directory structure doesn't comply with the Linux Standard Base. However, according to Dolstra this is not such a big problem as it seems:

We try to adhere to the LSB as much as possible with respect to /var and /etc and so on. But the disappearance of /bin, /sbin, /lib and so on is inherent to our system: if we would follow the LSB there, then it would be much more difficult to support multiple software versions and rollbacks. NixOS was an experiment to see how many difficulties there would be in practice. It seems that most Unix packages don't have many hardcoded references to specific paths because they are not identical between different Unix platforms anyway. And most hardcoded paths can be fixed easily in a Makefile.

The future: declarative specifications of networks

Of course the work on NixOS is ongoing. The developers are currently working on a branch named modular-nixos to make it easier to extend NixOS with hardware support or system services. There's also a research project about distributed deployment. Dolstra explains this:

As the configuration.nix file in NixOS is essentially a declarative specification of one machine, it would be natural to extend this to networks of machines. The user would describe then for example that machine X has to run a PostgreSQL database and machine Y an Apache web server. Based on this specification the user then should be able to automatically install all these machines, or generate virtual machines to test this network locally.

Conclusion

The purely functional model of Nix and the cryptographic hashing scheme of the Nix store give the user some important features that are lacking in most Linux distributions. It makes one wonder why enterprise Linux distributions haven't picked up this approach (or a more LSB-compliant version of it). A drawback is the amount of disk space and bandwidth used when upgrading a fundamental dependency. Perhaps the most pressing issue is that it requires a radically different mindset from the user.


(Log in to post comments)

NixOS: purely functional system configuration management

Posted Jun 18, 2009 7:43 UTC (Thu) by cyperpunks (subscriber, #39406) [Link]

store has some of the same features: http://www.pvv.ntnu.no/~arnej/store/

NixOS: purely functional system configuration management

Posted Jun 18, 2009 7:57 UTC (Thu) by michaeljt (subscriber, #39183) [Link]

My personal feeling is that it would be easier to achieve a reproducible setup with existing distributions if packaging practices were less messy. The current practice of running scripts as root when a package is installed, as well as messing with /etc, which should actually be reserved for configuration by the system owner/administrator (possibly using tools of course) is not good for reproducibility. OpenSolaris for instance now forbids postinstall scripts. Packages which really need to do things post-install must install a service or similar to handle that. I would love it if say Debian adjusted their policy to discourage installation scripts and files dropped into/changed in /etc at install time. I am sure this would be manageable on a medium term basis.

NixOS: purely functional system configuration management

Posted Jun 18, 2009 12:27 UTC (Thu) by skvidal (subscriber, #3094) [Link]

"System administrators update them in-place with various administration commands, e.g. a RPM or APT package manager or a configuration tool such as Cfengine."

Just to keep this particular meme from continuing:

rpm and dpkg are comparable commands, not rpm and apt.

NixOS: purely functional system configuration management

Posted Jun 18, 2009 12:54 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]

Neat project!

However, some things strike me as unnecessary. Building packages from sources can be avoided with the help of retargetable binary packets.

It can also allow slow migration of existing packets (first, make deb/rpm packages retargetable, then move to Nix).

NixOS: purely functional system configuration management

Posted Jun 18, 2009 16:39 UTC (Thu) by ajb (guest, #9694) [Link]

A similar system is vesta: www.vestasys.org

That's a purely functional build system, but under the hood it's also a package manager of a kind. You can actually import rpm-s and debs into it.
I don't think anyone has actually made a distribution using it, but it could be done.

It seems to me that nix is not so well engineered as vesta underneath. vesta has its own filesystem, and can present multiple views of the world in their own chrooted environments.

NixOS: purely functional system configuration management

Posted Jun 19, 2009 7:43 UTC (Fri) by sasha (subscriber, #16070) [Link]

I failed to understand how this distro helps with managing configuration files. "upgrading a configuration is as safe as installing from scratch" makes me think that it is completely my responsibility to copy all my configuration files to the new version of a program being upgraded. I'm not sure it is correct understanding... Does this distro have any helpers to merge my changes in the config file with the upstream ones? Is it managed in "functional way"?

NixOS: purely functional system configuration management

Posted Jun 19, 2009 16:42 UTC (Fri) by Darkmere (subscriber, #53695) [Link]

Please correct me if I'm wrong, but doesn't this approach completely remove the win to be had by dynamic linking in favour of static binaries (over time) until you have rebuilt everything in a boostrap manner to reduce the duplications and have a single set of base+libraries?

In one way it strikes me as a step forwards, and in another a huge step back. The question is if it's a net move forwards.

NixOS: purely functional system configuration management

Posted Jun 20, 2009 21:07 UTC (Sat) by njs (guest, #40338) [Link]

Yes, that's exactly what it does. Whether this is good or bad depends on the situation. You lose the ability to drop an upgraded library into place and have everything start using it immediately. Of course, that only works in the first place if you assume that the new library *really is* compatible with your old binaries, which is often assumed but rarely guaranteed. With the NixOS approach you gain the ability to test and switch programs over to the new library one at a time in a controlled fashion, keep some particular critical program using a fixed version of a library while the rest of the system upgrades, etc. Win some lose some.

NixOS: purely functional system configuration management

Posted Jul 4, 2009 12:36 UTC (Sat) by oak (guest, #2786) [Link]

> Of course, that only works in the first place if you assume that the new
library *really is* compatible with your old binaries, which is often
assumed but rarely guaranteed.

Nowadays Debian provides some support for this. You can easily check
whether any of the symbols changes at build time and build will fail if
they changed, but the library version didn't.

NixOS: purely functional system configuration management

Posted Jul 5, 2009 3:33 UTC (Sun) by njs (guest, #40338) [Link]

That could mean two things: either there's a tool to check that whenever you depend on the existence of a symbol _FOO, there is indeed a symbol _FOO. Or, whenever you used to depend on the existence of a symbol _FOO, not only does _FOO still exist but that it refers to the "same thing". (E.g., if it points to a function, that that function's assembly is unmodified. And all the functions that it calls are, in turn, unmodified.)

I assume you mean the former. Such functionality is handy, but it's exactly what I'm referring to by "assumed" compatibility -- hey, the names are right, it must work! If the library author and the application author did their jobs perfectly then of course this is true, and often it works out well enough in practice, but... the value you see in something like NixOS will depend on how willing you are to assume that's the case.

Personally, I consider our present approach to be a lamentable imperfection imposed by a world of constant security updates, limited bandwidth, and awful QA capabilities. I'm not sure when or if I'll be able to switch to a NixOS-style system, but I'm glad someone is reminding us of this fact.

NixOS: purely functional system configuration management

Posted Jun 26, 2009 1:12 UTC (Fri) by TRauMa (guest, #16483) [Link]

What I don't quite get is how this helps with the kind of upgrades we are actually doing - security upgrades mostly. In those cases we never want to roll back, we don't want to switch over gradually, we basically want to apply a single well-contained change (that comes pre-tested and hopefully doesn't break anything) globally and right now. All the things NixOS provides you can have with a modern Gentoo installation and careful snapshotting, except the possibility to have a system where every step of the upgrade is atomic and the system as a whole is still in a well defined state. But in critical setups you'll have two systems anyway, one where you test the change and one in production. And with security updates you are in a "bad state" as soon as the security issue goes full disclosure (ok, you learn that you were in a bad state all the time) and no intermediary step is interesting until you upgraded all consumers/dependencies of the packet in question. From this view a half-upgraded NixOS is working, but insecure, while a half-upgraded Gentoo is perhaps not working and insecure. But how does "working, insecure" help I wonder.

NixOS: purely functional system configuration management

Posted Jun 26, 2009 12:30 UTC (Fri) by Duncan (guest, #6647) [Link]

In addition, there seems no provision for the customizability that is the
forte of Gentoo and a good portion of the benefit of building from source
in the first place. Either that, or that aspect simply wasn't covered.

Where's the system default CFLAGS/CXXFLAGS (Gentoo's make.conf settings),
with the ability to override them per-package (Gentoo's /etc/portage/env),
without having to edit the pre-packaged nix expressions (Gentoo ebuilds)?
Where's the ability to specify compile-time dependencies (Gentoo USE
flags, both make.conf and package.use), again without having to edit the
pre-packaged nix expressions?

Maybe nix has that and it simply wasn't covered, as customizing to that
degree isn't something the binary distributions could do. But it's a
major benefit to building from source, which nix does, so it'd have been
nice to have a description of how that's handled, or a definite no, it
doesn't handle that. Without it, tho, I don't know as I'd consider it
worth the trouble to run compile-from-source, as that really is one of the
biggest benefits of doing so.

Meanwhile, Gentoo has config-protect functionality, with rollback if
desired, depending on one's choice of config reconciliation tool. And
binpkgs allow reasonably easy no-recompile rollback to previous package
versions for the binaries, while keeping them in a centralized location.
While centralized does mean everything uses the same library version,
there are tools to resolve breakage, automating the recompiles, and
it /does/ eliminate the security updates issue others mentioned for NixOS.

Now Gentoo does not have per-user installations and user installable
packages by default, but the Gentoo/prefix project addresses that,
allowing package installation at arbitrary prefixes, including home dirs,
for various Linux and non-Linux (FreeBSD, etc) installations.

Still, NixOS is using a very interesting idea, and as it matures, it could
well give Gentoo and other build-from-source distributions a run for their
money. If the (currently) much more mature Gentoo wasn't around filling
my needs better, I could certainly see giving NixOS a try, and who knows
what'll happen over a few years? As I said, I could see it giving Gentoo
a run for its money. Its devs definitely have the guts it takes to go
against the flow and develop something that really does fill a niche
filled imperfectly if at all by others, and as such, certainly has the
potential to become the leading from-source distribution, a position
Gentoo has filled for most of this century so far.

Duncan

NixOS: purely functional system configuration management

Posted Jul 4, 2009 12:40 UTC (Sat) by oak (guest, #2786) [Link]

> By not storing components such as libraries, header files or programs in
global locations, all packages are forced at build time to use a specific
version of their dependencies, located in the Nix store. To make this
happen, the developers have patched Glibc, GCC and the dynamic linker ld
to not search files in any default locations.

Something similar can be done also with scratchbox2 without a need to
patch Glibc, GCC & ld: http://packages.debian.org/squeeze/scratchbox2

(As it uses LD_PRELOAD, it works only for dynamically linked binaries or
scripts having dynamically linked interpreters.)

NixOS: purely functional system configuration management

Posted Jul 4, 2009 15:48 UTC (Sat) by astrashe (guest, #59430) [Link]

This strikes me as being really useful, especially for clouds or large server farms. There's a lot of value in having a server be in a known state.

It also seems to me that it fits in to some other changes that seem to be taking place. We used to think of computers as things, physical objects. But to a certain extent, "a computer" is becoming a blob of data that exists in some sort of container (ie., hardware).

My main desktop, for example, is a blob of data that lives at a VPS hosting company. Some people carry around their systems on USB sticks, and boot them wherever they happen to be. Etc.

If a computer is a blob of data, then having better tools with which we can describe and build these blobs is a really good thing.

Dispelling some misconceptions

Posted Mar 30, 2010 6:54 UTC (Tue) by rpwoodbu (guest, #64843) [Link]

This article is beginning to show its age, but since it is one of the top
hits for Nix, I thought it is worth dispelling some misconceptions some of
the comments on this article might convey, as people eager to learn about
Nix may find themselves here.

Misconception #1: NixOS is a source-based OS.

Yes and no. Nix offers an elegant approach to building, installing, and
distributing software. The elegance stems from all install having the
_ability_ to build from source, but enjoying the _optimization_ of
downloading prebuilt binaries if they are available. User tweaks can be
made to some packages at install time (perhaps to include or remove a
compile-time feature), and if a prebuilt binary doesn't exist with exactly
those same tweaks, Nix will necessarily build it from source. In most
cases, though, the optimization kicks in and prebuilt binaries are
efficiently downloaded and installed, just like with a binary-based
distribution. It is the best of both worlds.

Misconception #2: Nix eliminates the benefit of shared libraries.

Nix still uses shared libraries extensively. Programs are not ordinarily
built statically. If two programs are built against exactly the same
version of a library, they will share it, conserving disk and memory
resources. But in the interests of full reproducibility, you are not meant
to modify the shared library in-place. If a library needs to be repaired
(perhaps to close a security hole), all packages that depend on it must be
rebuilt to enjoy the use of the new library. Nix makes this quite simple:
after a library package is updated, an ordinary "upgrade" action will
rebuild everything necessary. After that, you can run a garbage collection
operation to free the disk space used by all the old software. And,
perhaps more importantly, you can roll back to the old software easily
(assuming you haven't run the garbage collector) should you find the update
to be unsatisfactory. Yes, it takes more time to effect an upgrade if you
have to rebuild a lot of software. There are usually binaries available,
which automatically speeds up the process considerably.

The best part is that you can have both minor versions of the library
installed simultaneously, and choose to have some programs use one and
others use another. This can be especially useful if a security fix breaks
some security-insensitive software; for instance, a libpng exploit means
needing a new browser, but perhaps not needing a new version of a game that
doesn't read "wild" files.

Misconception #3: System X can already do something Nix can do.

Yes, there are other systems, like Store, like Gentoo, that can do _some_
of the things Nix can do. But Nix can only really be understood if you
look at the arc of functionality it provides: functional build expressions,
identification of builds by a cryptographic hash of its build inputs,
suppression of spurious (i.e. external) dependencies, binary distribution
as an optimization of building, etc. What emerges is a packaging system
(and ultimately a distribution) that is hard to sum up succinctly, and is a
different way of thinking about maintaining a computer system, but which
has properties of stability, reproducibility, and operational guarantees
that are hard to match.

Final thoughts:

Give it a go. It is easy. You don't have to use the whole distro; just
install the Nix package manager on whatever Linux distro you're running.
Except for the package manager code itself, which is conveniently available
in several popular package formats, the entirety of it installs by default
in /nix (you can install it anywhere you like, but you need to use /nix to
enjoy prebuilt binaries), so it is not going to interfere with anything.
Because it is fully contained, it is distro-agnostic. It is a great tool
to have in your back pocket when dealing with older installations that just
don't have the libs you need to run the code you want. My favorite example
was a situation where I was running a newer kernel on an old RHEL 4 box,
where upgrading wasn't an option, but I really needed online ext3 resizing.
The userspace tool resize2fs didn't support it on RHEL 4, because Linux
2.6.9 didn't have it anyway, and that was the official kernel version for
RHEL 4. The newer resize2fs depended on a library that wasn't available
for RHEL 4. I couldn't upgrade or reboot the box. But I was able to throw
Nix on there quickly (I installed it in /tmp to avoid any mishaps) and
installed its resize2fs, which had the feature I needed (and the right
libs). It saved me the huge trouble of trying to get the new resize2fs
built on this crusty old machine, and I didn't have any downtime.

Copyright © 2009, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds