Context

I’ve been a self-diagnosed Linux “distro hopper” for many years. Having played with Ubuntu, KDE Neon, Fedora, Debian, Pop OS, Linux Mint, Kali (formally known as BackTrack), Puppy Linux, and most importantly Hannah Montana Linux. Some I’ve enjoyed more than others, but none of them ever felt like a perfect fit.

One of the biggest challenges I faced as a distro hopper was the repetitive task of configuring each new installation exactly how I like it. This process ranged from tweaking desktop environment settings to installing and configuring my preferred applications. The tedium of this process led me to seek automation, resulting in my first commit to my toolbox repository in April 2019.

The Evolution of My Toolbox

My toolbox repository has undergone several transformations over the years:

  • It started as a collection of custom bash scripts for configuring fresh Ubuntu installations
  • Later, it evolved into an Ansible-based configuration management system
  • Now, it’s entering its latest phase with NixOS, which has fundamentally changed my approach to system configuration

Testing these configurations was always a challenge. I would typically spin up a bare-bones Ubuntu VM to verify my changes - not quite a test suite, but a practical way to ensure things worked before breaking my main system. However, this process was fragile and easily disrupted by Ubuntu updates.

The Package Management Puzzle

Managing application installations across different systems forced me to juggle multiple package managers and installation methods. I developed a hierarchy of preferences:

  1. apt (via default packages or an external PPA)
  2. flatpak (via flathub)
  3. raw binaries
  4. building from source

This fragmented approach created its own challenges. For example, installing Firefox Nightly meant placing it in ~/Apps/Firefox and adding ~/Apps to my PATH. While Firefox could update itself, many other applications required manual tracking of installation methods and update checking. This complexity made my toolbox repository increasingly brittle over time.

Discovering NixOS

In March 2024, five years after starting my toolbox repository, I discovered NixOS. At its core, NixOS is:

A Linux distribution that leverages an immutable design, with atomic updates. Its use of a declarative configuration system allows reproducibility and portability.

Let’s break down what this means for everyday users:

  • Immutable design: System files cannot be changed after they’re installed, preventing accidental modifications
  • Atomic updates: System updates either completely succeed or fail, never leaving you with a partially updated system
  • Declarative configuration: You describe what you want, and NixOS figures out how to make it happen

The NixOS Configuration Approach

NixOS configuration revolves around two primary files:

  1. configuration.nix: Defines your system’s configuration, including:
    • Installed packages
    • System settings
    • Service configurations
  2. hardware-configuration.nix: Specifies hardware-specific settings like:
    • Drive configurations
    • Mount points
    • Hardware-specific drivers

This file-based approach sets NixOS apart from traditional Linux distributions, which typically rely on various configuration files scattered throughout the system.

Configuring NixOS

The power of NixOS’s declarative configuration cannot be overstated. Here’s a practical example of how to install user packages:

  home = {
    username = "${vars.user.name}";
    homeDirectory = "/home/${vars.user.name}";
    packages = with pkgs; [
      firefox
      obsidian
      ollama
      vscode
      hugo
      # etc...
    ]
  };

This snippet uses home-manager, a popular tool for managing user environments. The NixOS package repository currently hosts over 120,000 packages, making it one of the largest package managers available.

Understanding Flakes

While technically an experimental feature, Flakes have become a cornerstone of modern NixOS configurations. Think of them as the equivalent of:

  • Python’s requirements.txt
  • JavaScript’s package-lock.json
  • Ruby’s Gemfile.lock

Here’s a simple example of a flake configuration:

{
  description = "My system configuration";
  
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, home-manager, ... }: {
    # Your system configuration here
  };
}

This approach ensures that if your system builds today, it will build exactly the same way tomorrow - addressing the fragility issues that plagued my previous toolbox approach.

The Power of Rolling Back

NixOS includes a robust rollback system as a core feature. When you boot your system, you’ll see a GRUB menu listing your current and previous system configurations, which looks like:

This means you can:

  • Safely experiment with system changes
  • Quickly recover from broken configurations
  • Test new setups without fear of system breakage

Nix Shell: Development Environments Made Easy

NixOS’s shell feature provides isolated development environments on demand. Here’s a practical example:

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = [
    pkgs.git
    pkgs.vim
    pkgs.just
    pkgs.tmux
  ];
}

This feature is particularly valuable when:

  • Testing new tools before system-wide installation
  • Working on projects with specific dependency requirements
  • Maintaining clean development environments for different projects

For quick experiments, you can also create temporary shells:

$ nix-shell -p git -p vim -p just -p tmux

Real-World Implementation

I’ve successfully deployed the same NixOS configuration across diverse environments:

  1. A Thinkpad T420 laptop
  2. A Proxmox VM
  3. A Macbook Pro VM via UTM
  4. Windows 11 via WSL2

This consistency across platforms demonstrates the true power of NixOS’s declarative approach. When I make changes on one system, I can easily sync them across all my environments through my Git repository.

The Learning Curve and Challenges

While NixOS offers powerful features, it’s important to acknowledge its learning curves:

  • The Nix language can be intimidating for newcomers
  • Documentation can be lacking
  • Some common Linux tasks require learning new approaches

However, these challenges are offset by the benefits of reproducibility and stability.

Conclusion

My journey from distro hopping to NixOS has fundamentally changed how I think about system configuration. While I’m still learning the intricacies of NixOS, its declarative approach and reproducibility have finally given me the stable, maintainable system I’ve been seeking.

For those interested in exploring NixOS:

Perhaps most importantly, NixOS has helped me realize that the perfect Linux setup isn’t about finding the right distro - it’s about having a system that can consistently reproduce your perfect environment, wherever you need it.