Future-proof your skills and escape the tech hamster wheel with Linux, Python, vim & git (LPvg) including NixOS, Jupyter, FastHTML and an AI stack to resist obsolescence.

Getting Started with Nix Flakes

This article describes my journey to integrate Nix Flakes into my NixOS configuration for better version control and reproducibility. Initially hesitant due to the perceived complexity, I eventually embraced Flakes for their ability to simplify git management and streamline my workflow. The article provides a step-by-step guide on implementing Nix Flakes, moving the system configuration to user space, and rebuilding the system for a more manageable and reproducible development environment.

Introduction

From tinkering with Levinux to embracing NixOS, I’ve been on a quest for the holy grail of reproducible, timeless computing environments. Now, with Nix Flakes, I’m taking another leap into the future of system configuration. Join me as I unravel the complexities, face my initial skepticism, and discover how Flakes might just be the missing piece in our perpetual tech puzzle. Spoiler alert: It’s not as flaky as I first thought!


Getting Started with Nix Flakes

I discovered NixOS some months ago and recognized a kindred spirit. Some 15 years ago, I started a project called Levinux that tried to make a completely portable minimal Linux system for cutting through the ages that would play host to such roles as my main development system and home of a private journal. I wanted to make my daily work routine timeless so I could get better and better at a smaller set of muscle memory skills over time. Muscle memory, I deduced, was the path to forever improving craft in tech.


The Quest for a Timeless Development Environment

But I based Levinux on ye old emulator, QEMU, and it lacked performance and viability as a main day-to-day platform. It was like a lowest common denominator virtual machine that used an extremely tiny Linux (Tiny Core Linux) based on the premise that all other “host” systems (Macs, Windows, and other Linuxes) could always easily run it. The flaw in this was that, though it was like a neat magic trick, the performance was terrible, and it was not suitable as a daily platform for work. Whatever I chose had to hold its own against anything else out there.


The Disappointment of Virtual Machines and Containers

Of course, one’s mind goes to more powerful virtual machines like VirtualBox or containers like Docker or LXC/LXD. But over the years, both virtual machines and containers have disappointed me. You’ve now got these big virtual images that can get lost, trashed up, or just weigh you down with all the extra steps and tooling. There was always a “host” machine that was something else. While Levinux was a VM technology too, it built entire systems from scratch with recipe files. And I learned Nix was a language to do this, and NixOS was an OS built from it, and the benefit could be realized on Macs and Windows too in folders where you could do similar builds.


The Promise of Nix: A Deterministic System Definition

So I realized a language exists for you to deterministically define a whole system with a text file! Or you could determine “flakes” of a system and instantiate them under Macs and Windows. It’d be like a bash file that does a whole bunch of sequential apt installs without version issues because everything’s pinned. In such a scenario, you can just rebuild a system anytime, anywhere, and on almost anything. Hardware changes. Platforms change. But a phantom system can cut through it all in time and space by nixing all those versioning issues for Unix-like development.


Embracing the Nix Ecosystem

What a brilliant name for a brilliant system. I would have preferred if Nix were just Python, but as it is, it’s a new language similar to Haskell specifically for configuring systems in a way that fixes the “not on my machine” problem. It not being Python, it’s going to take some work for me. I’m a confirmed monoglot. I speak English-only, in terms of spoken languages, and I speak Python only when I can help it, in machine terms. Much doesn’t fit my head. Python does, just barely. Taking on learning this system configuration language is a challenge for me, but one that is worth it.


Understanding Nix: Language, OS, and Package Manager

Nix is three things: a language, an operating system, and a package manager.

When Nix builds your whole OS, it’s called NixOS, and is basically a Linux distro, though there is some debate around whether it qualifies as a Linux distro, being so radically different from mainstream versions like Red Hat or Debian. The system is built from the software in the Nix package management system using the Nix language. There’s a triangle diagram out there that tries to explain it. It’s all so well thought out and has been around long enough to now be battle-hardened and time-tested. It’s a safe bet and like Levinux perfected.

Visualizing Nix Components

graph TD A["Nix"] --> B["Nix Language"] A --> C["Nix Package Manager"] A --> D["NixOS"] D --> E["Linux Distro"]

The Power of a Single Configuration File

A single file defines your system, which you can rebuild from frequently. So, for example, there are no apt installs or permanent pip installs. Instead, those directives go into configuration.nix if you want the changes to persist, and you rebuild your entire system—which takes much less time than you might think and can be done without restarting your system or even interrupting your workflow much.

And of course, you can temporarily install things and try them out before committing them to your system. So you know that feeling of gradually polluting your system over time from trying this or that, scattering libraries and files all over your system causing lurking problems? It just doesn’t happen on NixOS. Everything is deliberate, deterministic, and self-cleaning because the whole system gets rebuilt as if from scratch all the time.


The Challenge of Version Control for System Configuration

And that brings us to the purpose of this article. A large part of the appeal of NixOS is that the entire system gets defined and built from a single file /etc/nixos/configuration.nix. It’s super-easy to jump on the NixOS bandwagon this way, immediately tweaking out this file for your preferred environment.

But also being a text file as it is, one of the first things you want to do is put it in a git repo and perhaps push it up to GitHub so your system can become a living, growing document that’s with you for the rest of your life, potentially. Not only does Nix fix the “not on my machine” problem, but it makes your metaphorical machine into a summonable phantom that travels with you through life, instantiatable on any hardware like a spell of possession. And to manage this magic, you generally want git. /etc/nixos/ should be in a git repo, right?


The Root User Dilemma and the Introduction of Nix Flakes

This is where NixOS hits its first weakness in that /etc/nixos/ is a protected location belonging to the root user—and not the system user. This is logical and doesn’t seem bad at first because you can easily enough sudo to edit the files, but making the whole directory into a git/GitHub repo is challenging.

You immediately need special scripts to adjust file permissions and ownership in a parallel directory that you can edit so all your git tooling and configuration is used. But if you’re going to do that, why not just symlink to the new directory? Enter Nix Flakes.

While there are other benefits to flakes, it is this out-of-the-starting-gate gotcha that got me. And I was so hesitant because the initial beauty of Nix is in its simplicity. Nix-it-all! No more magical mixtures of apt-gets, pip installs, environment variables. Less surface area. Lower cognitive overhead. Set it all once in one place and always know where to look.


Overcoming Resistance to Nix Flakes

So I resisted Nix Flakes for months. Things are complicated enough not only switching off of Windows and Mac and onto Linux, but it’s switching onto a version of Linux where everything is different than a normal Linux. Normal Linux like Red Hat, Debian, or Arch use something called the Filesystem Hierarchy Standard (FHS) and all files are put in location.

On NixOS, while the FHS is still there, all the files are symlinked into location. You’ll hear the term immutable version of Linux. Like Tiny Core Linux that Levinux was based on, things just can’t get messed up because there’s a reliable system reset that nukes the place from orbit. It’s the only way to be sure.

Given all the radical new thinking required for a full OS-switch (which is actually not required for just using the nix command on Mac or Windows), I avoided the extra complication of a still optional and experimental feature of Nix called Flakes. In my mind, nix nixed the complication by gathering everything into one /etc/nixos/configuration.nix file, and flakes flaked out the simplicity. It was all named with such unintentional irony. Nix nixed the complications on Unix/Linux-like (*nix) systems, and Flakes, in my opinion, just made such a simple system flaky again. Why have to look in multiple places at multiple files when you could only look at one? But the truth is, it simplified getting configuration.nix into git.


The Revelation: Flakes Simplify Git Management

So, I’m coming around. Flakes don’t have to complicate things. Initially, the touted benefit of Flakes seemed only to be exact-version pinning when you distributed sub-chunks (flakes) of a system to other people on different systems so they could deterministically reproduce it. Now that’s awesome, but it didn’t justify complicating the simplicity and elegance of configuration.nix.

But what I discover is that flakes simply let you move configuration.nix out of system space and into user space so you don’t need any custom scripts or tooling to get it manageable under git. This was an awakening, and I’m switching over to Flakes now.


Implementing Nix Flakes: A Step-by-Step Guide

Here’s what I did in order to do it just once. You’ll notice that turning it into a git repo is a part of the process. The Nix Flake feature and using git repos seem very intertwined. If you use git all the time for its miscellaneous backup and deployment capabilities, the utility of flakes will be obvious.

Imagine a git repo carrying around everything it needs from the parent OS to get it working. Forget virtualenv, requirements.txt, pip, poetry, and the mishmash of commands and acrobatics you need to do to get things working. Just cd into the repo and type nix develop and you’re in business.

Steps to Implement Nix Flakes

Let’s get to it. You enabled flakes with that one line in your configuration.nix and rebuilt your system. Flakes are enabled but nothing seems to have changed. Open a Terminal and here’s what you do to get /etc/nixos/ out of system space and into user space, allowing it to be managed under git:

  1. Create a new directory for your NixOS configuration:

     mkdir ~/nixos-config
     sudo cp /etc/nixos/* ~/nixos-config/
     sudo chown -R $USER:users ~/nixos-config
     cd ~/nixos-config
    

    This makes a copy of your /etc/nixos/ location.

  2. Add a minimal flake.nix file:

    I don’t know if this is technically even necessary for this use case as the symlink might be enough for sudo nixos-rebuild switch, but let’s put it there anyway because it’s how flakes in general work.

     # flake.nix
     {
       inputs = {
         nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
       };
    
       outputs = { self, nixpkgs }: {
         nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
           system = "x86_64-linux";
           modules = [ ./configuration.nix ];
         };
       };
     }
    
  3. Initialize a git repository:

     git init
     git add .
     git commit -m "Initial commit"
    
  4. Check that everything’s in place:

     nix flake check
    

    Resolve any errors. If everything checks out, proceed to the next step.

  5. Backup and symlink the new configuration:

     sudo mv /etc/nixos /etc/nixos.bak  # Backup old config
     sudo ln -s /home/username/nixos-config /etc/nixos  # Symlink new config (replace 'username' with your actual username)
    
  6. Rebuild your system:

    The new command that will build your system if you hadn’t symlinked is:

     sudo nixos-rebuild switch --flake /home/username/nixos-config#nixos
    

    But because of the symlink, you can continue to use:

     sudo nixos-rebuild switch
    

Conclusion: Embracing Nix Flakes with Confidence

And so, I have turned on the Nix Flakes optional feature, switched my configuration.nix over to be more easily managed with git, and my workflow has not changed at all.

The additional cognitive overhead and constant additional friction to process that I had feared with Nix Flakes hasn’t yet materialized. So I can now cautiously approach the new Flake features, which I actually do need for sharing work with my coworkers, without being overwhelmed by exploding complexity.


Visual Summary of the Journey

graph TD A["Levinux Project"] -->|"Quest for timeless computing"| B["NixOS Discovery"] B --> C{"Initial NixOS Setup"} C -->|"Simple but limited"| D["Single configuration.nix"] C -->|"Desire for version control"| E["Git Management Challenges"] E --> F{"Nix Flakes"} F -->|"Initial skepticism"| G["Perceived Complexity"] F -->|"Realization"| H["Simplified Git Management"] H --> I["Move Config to User Space"] I --> J["Enable Flakes Feature"] J --> K["Create flake.nix"] K --> L["Symlink to /etc/nixos"] L --> M["Improved Workflow"] M --> N["Version-Controlled Config"] N --> O["Easier Sharing and Reproducibility"] style A fill:#f9f,stroke:#333,stroke-width:2px style B fill:#bbf,stroke:#333,stroke-width:2px style F fill:#bfb,stroke:#333,stroke-width:2px style M fill:#fbb,stroke:#333,stroke-width:2px