Date:
Fresh OS installs are bliss. But the joy fades quickly as
installing and uninstalling programs leave behind a trail of
digital debris. Even configuration management and declarative
systems like NixOS miss crucial bits, like the contents of
/var/lib or stray dotfiles. This debris isn’t just
unsightly. It can be load-bearing, crucial to the functioning of
your system, but outside of your control, and not preserved on
rebuilds. Full system backups merely preserve this chaos. I
wanted a clean slate, automatically, every boot.
“Erase
your darlings” inspired an idea in the NixOS community:
allowlisting files and directories that persist across reboots.
Anything not on the list gets wiped. The simplest implementation
involves mounting / as a tmpfs (i.e. in RAM),
and then bind-mounting or symlinking the allowlisted items to a
disk-backed filesystem.
This has been implemented for NixOS in the impermanence project, and has some support in Guix too. In NixOS, declaring this allowlist looks like:
environment.persistence."/mnt/btrfs/persistent" = {
directories = [
"/srv/git"
"/var/lib/private/kea"
];
};
After three years of running this setup across my NixOS desktop, laptop, home NAS, router, and VPS, I’m sharing my experience. Below is a consolidated view of everything I persist across my machines (each has its own unique allowlist).
/boot, needed for boot./etc/NetworkManager/system-connections, WiFi
connection data. I want my airport WiFi to work after a reboot,
and managing this declaratively is a pain./etc/ssh/ssh_host_*_key, SSH host keys./nix, the Nix store, containing all my
binaries and configurations./srv/git, my private Git repositories./srv/nas, my NAS: Big Buck Bunny in a variety
of encodings./var/lib/NetworkManager/secret_key, for stable
RFC7217
addresses.
/var/lib/audiobookshelf, and various other
directories for stateful services./var/lib/private/kea, my router’s DHCPv4
leases./var/lib/systemd/timers, the last time
systemd’s cronjobs ran./var/lib/systemd/timesync, recent time, useful
for machines without an RTC.~/.cache/restic, Restic backup cache.~/.config/Signal, Signal Desktop data.~/.emacs.d/init.el, my Emacs config, which
lives outside of Nix so I can easily use it from non-Nix
machines.~/.emacs.d/projects, Emacs stuff.~/.gnupg/private-keys-v1.d/, my GPG keys, used
for Password Store.~/.password-store, my Password Store
repo.~/.rustup, so people think I’m a pretty cool
guy.~/.ssh/id_ed25519_sk, my SSH key (Yubikey
handle).~/Downloads, browser downloads.~/persist, my personal files, source code,
docs, etc.Everything else, which includes things like:
/etc (mostly). NixOS manages parts of
/etc declaratively, but the rest is
ephemeral./var/cache, caches./var/lib (most of it), ephemeral application
state./var/log, my machine doesn’t persist logs
between reboots. This wasn’t a goal, I just haven’t needed to
persist logs./var/nixos, NixOS-specific data for stable
UID/GIDs.~/.config (mostly), etc. Managed dotfiles are
handled by Nix. Others are ephemeral.~/.mozilla, my stateless Firefox profile (more
on that in a future post).~ (mostly), I explicitly use
~/persist for important data.(Note: /usr is absent on NixOS systems.)
Peace of mind: My machines are defined by the tuple (nixpkgs commit, config commit, state). That “state” is now tiny, around 1MB, compared to the usual 20-30GB root filesystem. This eases anxiety about system drift. Bliss!
Fearless experimentation: Trying out new software or configurations no longer leaves ruins in its wake. If things get hairy, a quick reboot brings me back to a clean slate.
Easy reproducibility: When I encounter a bug, a reboot confirms whether it’s related to persistent state or a genuine issue. This makes bug reporting and collaboration easier.
Trivial backups: Backing up my machines (excluding the NAS) is quick, thanks to the minimal state.
Clear path to statelessness: The allowlist highlights opportunities to move even more state into declarative configuration. Resistance is futile!
Inconvenience: Persisting data requires conscious effort. It’s not always obvious what needs saving.
Installation complexity: Standard OS installs are simple: boot partition, a root partition, done! But my disk partitioning is now a bit more complicated. While disko helps, it adds to the learning curve.
Undefined behaviour: Applications aren’t designed for users to wipe half their state and though I haven’t run into problems, I expect things would break in unexpected ways.
Performance problems: I’ve noticed bindfs occasionally consuming lots of CPU. While some optimizations have been suggested, I suspect I’ve been living with slower I/O for a while now.
Declarative machine management and ephemeral state has banished my anxiety about stateful systems, a huge win. But this peace of mind comes at a cost: time. Ironically, despite aiming for less upkeep, I likely spend more time than users of conventional systems.
My aim is to simplify maintenance. While abandoning impermanence entirely (or switching to something like Fedora Silverblue) is an option, I’m taking a more gradual approach: expanding what I persist, starting with my Firefox profile. I’ll share my experience with ephemeral Firefox soon.