On Living with tmpfs
A year ago I blew away my root partition and replaced it with a tmpfs. Every boot, the system starts from nothing. Every shutdown, everything not explicitly declared is gone. I have not regretted this once.
That’s the pitch. The year taught me what it actually costs.
What “impermanence” actually means
The term gets thrown around in NixOS circles like it’s a philosophy. It is, kind of. But concretely: your root filesystem lives in RAM. On boot, it’s empty. NixOS activates and populates it from the store. /nix/store is mounted from your actual persistent disk, and symlinks go everywhere. /etc, /run, the usual skeleton. But anything your running system writes outside of explicitly persisted paths? Gone on reboot.
No accidental state, no config drift, no “I changed something six months ago and now I don’t know what this system actually is.” You know what it is. It’s your config, exactly, and nothing else.
The mental model shift is the hard part. You stop thinking about your system as a thing that accumulates state and start thinking of it as a thing that gets instantiated from a spec. Like a container that happens to run your desktop. You are not managing a living system anymore. You are authoring a declaration and the system is the output.
This maps perfectly onto how NixOS already wants you to think. Impermanence is just taking it to its logical extreme.
My config lives at lowcache/volnixos if you want to read the actual implementation rather than the prose version.
What you have to persist
This is the real work. Everything your system needs to remember across reboots has to be explicitly listed. Using impermanence, you declare bind mounts from a persistent volume (usually /persist) into the live root.
Here’s what my persistence list actually looks like, roughly:
environment.persistence."/persist" = {
hideMounts = true;
directories = [
"/var/log"
"/var/lib/bluetooth"
"/var/lib/systemd/coredump"
"/etc/NetworkManager/system-connections"
{ directory = "/var/lib/colord"; user = "colord"; group = "colord"; mode = "u=rwx,g=rx,o="; }
];
files = [
"/etc/machine-id"
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
"/etc/ssh/ssh_host_rsa_key"
"/etc/ssh/ssh_host_rsa_key.pub"
];
};
Per-user, the list grows:
- Shell history. Obviously.
- Browser profiles. Firefox keeps its entire state in
~/.mozilla. If you don’t persist it, you log back into everything every boot. - SSH keys (
~/.ssh). Not the host keys (those are system-level), but your personal keys. - GPG keyring.
- Game saves. Steam puts them all over the place. GOG is worse.
~/.local/share/recently-used.xbelif you care about that sort of thing. I don’t, but it came up.- Password manager database if you’re not using a synced service.
- Anything that acts as a local database: Obsidian vaults, local email, that SQLite file your app dumps in
~/.local/share/whatever.
The first pass you always miss something. You find out at the worst time. That’s the deal.
What you don’t persist is everything else, and everything else is more than you think. Application caches, thumbnails, stale lock files, that .DS_Store equivalent your cross-platform app dropped, the config file you edited once to try something and then forgot about. All of it, gone. Silently. Correctly.
What you stop thinking about
After the initial setup tax, a surprising number of problems just disappear.
Stale state causing weird behavior? Gone on next boot. Application leaving behind garbage that interferes with upgrades? Not a problem. “Works on my machine but only because my machine has three years of accumulated cruft” is eliminated as a failure mode.
The reproducibility story becomes real. When I say my system is defined by my config, I mean it in a way that actually holds up. I can read volnixos and know exactly what my system is. Not approximately. Exactly.
There’s also a psychological shift. I’ve become a lot less precious about configuration. Want to try something? Try it. Worst case, reboot. The system resets. Nothing festers.
Why niri fits this model
I run niri as my window manager. Scrollable, columnar tiling. Wayland-native. It’s an unusual choice and I think it’s the right one for this setup.
Most tiling WMs carry state. Saved layouts, workspace configs, persistent scratchpads, rules that accumulate over time. With impermanence, all of that would reset on every boot anyway. So you want a WM where the behavior is the config, not a state file.
Niri is exactly that. The layout model (infinite horizontal scroll, windows in columns) is a pure behavior derived from your config and your current session. There is no “layout file” to persist because the layout is the rules. Open a terminal, it appears. Open a browser, it goes in a column next to it. The spatial logic is deterministic from the WM rules alone.
A declaratively managed system deserves a declaratively behaving WM. Everything else is fighting your own setup.
It also helps that niri is simple. Small surface area. Well-defined behavior. On a system where I want to know exactly what everything is doing, running a WM that doesn’t do surprising things counts for a lot.
Shell init
Minor thing, but: I use volinit as my shell fetch and init display. It rolls into the terminal on a new shell session. Because when everything starts from nothing on every boot, a little confirmation that things came up correctly is quietly satisfying. The system rebuilt itself. Again. Here’s the proof.
It’s a small thing. It matters more than it should.
What it costs
The first week is rough.
You will forget to persist something important. You will lose data that you thought was persisted but wasn’t because the path was slightly wrong. You will reboot and find that your browser thinks it’s a fresh install. You will SSH into your machine from somewhere and realize the host keys rotated because you forgot to persist them, and now you have a scary warning in your terminal and feel like an idiot.
The impermanence module has good docs. Read them before you start. Think carefully about your user home directory persistence setup. The home-manager integration is slightly different from system-level persistence, and conflating the two costs you an afternoon.
The setup cost is real. A few days of methodical work, honestly. But it’s a one-time cost. After that, the system is stable in a way that conventional setups aren’t: not because nothing changes, but because every change is explicit, tracked, and intentional.
I can’t go back to accumulating state on a root partition and hoping it stays coherent. That’s not a system. That’s an experiment that’s been running too long.
Config: lowcache/volnixos
Written from the machine it describes. More field notes: the archive · RSS.
Working on something in this territory? I take on scoped engagements.