How should I install software?

After reading the manual it seems like only the system packages are maintained in declarative style while ad-hoc installations are not.

Does that imply that if possible all packages should be installed via the

  environment.systemPackages = with pkgs; [
    wget vim


I’m not using nixOS currently, but Arch Linux + nix.

After an initial phase during which I played around with nix-env, I have to be honest… I do not use it anymore…

Currently I keep “global software” that my wife or my son would need installed globally via pacman. On nixOS, I’d probably install those through configuration.nix, wither via systemPackages or an appropriate option.

Software I use only on my local user, but not my wife or my son, like emacs, is installed and configured through home-manager. My wife (without sudo) is happily using nix-env, my son still has to ask for his software (which I exclusively install via pacman, as a dependency to a local empty package, which is kind of declarative, this is easier to do for me via SSH even on a mobile, and I do not need to impersonate him to do so).

Last but not least, software I only need in a project, is managed through a project local shell.nix.

I have to say, I’d try to avoid systemPackages/home.packages and prefer options from modules if available, and if they are missing, I’d wrap it in a local module ASAP. I really like those lists to be empty.

Hm…ok let me ask a follow-up question then which might be a bit too far out:

What is the advantage compared to just using Arch with pacman for everything?

I have my user config in git and am slowly converting from a gitrepo that has a collection of text files in random formats to self-written modules in home-manager, where I have a unified syntax for all configuration and a central place to look at.

Also especially shell.nix and a nix-shell --pure are perfect for sandboxing. I can learn in seconds about dependencies that I missed, and I can easily synchronise my shell.nixbuildInputs with the documented system dependencies. Without nix I had to resort to docker for this and had to constantly rebuild the image.

Together with direnv I can easily hop projects without having to remember running shell scripts to unload old environments and load new ones. Again, through shell.nix those environments are built using a unified syntax and config language rather than having to remember each language specific tools way to sandbox the things. And those tools will not even sandbox system dependencies, but only those from their respective packaging system.

Also, a long term goal is to eventually run nixos-infect (or just install nixOS and don’t boot into Arch Linux anymore)

The advantage is you can undo. You can always go back to a prior state if you’ve not garbage collected. That gives you the freedom to tinker fearlessly (even with the linux kernel).

In my book fearless tinkering is worth a lot.


There may be a good counter argument, but I treat nix-env as an anti-pattern. I use nix-shell or nix run to test something, run one-offs, or use project-specific software, and add global software to my config.


How does this work in general? I mean other programs don’t use this approach so at the latest as soon as I make a config change or add an account in Thunderbird this must fall apart, right?

That’s why you configure thunderbird via home manager.

I cannot begin to imagine how that would work. Don’t you have to somehow manually modify Thunderbird profile files?

Sadly there is no module available in home manager for thunderbird, but a lot of TUI mail user agents.

Usually they are then configured with a set of default values which can be overridden by options.

Home manager will write those resulting config files into the nix store and softlink them from where the program expects them to be.

Of course those are then write protected.

Actual maildata is written to a writable folder.

This sounds awesome but it also sounds really difficult. Is there something I could read as an introduction?

You aren’t crazy–this is a real problem to some degree. But it isn’t Nix-specific. It’s the legacy of taking a container, let’s call it a “data storage device”, and tossing in conceptually-different kinds of data with no great way to tell the types apart aside from knowing what is what. Backup/restore and file sync also struggle with these fundamental mismatches.

The big mess of “state” in a computer is really important, but the value isn’t evenly distributed. Copying the whole mess to another system isn’t very useful. Building the mess of state in each computer manually and consistently is a pain. Keeping the valuable parts in sync across different devices is still a hard problem.

Keeping an archive/copy/backup/repository of dotfiles is one traditional approach that partially addresses this problem, but dotfiles are somewhat divorced from what system they were running on. Nix/NixOS break off a big piece of this problem, making it possible to archive an executable description of what your system and software looked like alongside messier configuration state.

The quick-and-dirty answer: ferret out app config that has value and store it alongside your system config. As they evolve, commit the changes.

The other answer (I’ll resist characterizing it) is to move the config into Nix expressions in one form or another. I don’t really recommend this if you intend to share config with systems that aren’t NixOS, especially if you don’t intend to use Nix on them. Think about your lowest-common-denominator before you convert something.

IIRC there are at least two common ways to do this:

  1. Use the module system(s) (in NixOS, nix-darwin, home-manager, and probably elsewhere), which usually provide a simplified interface for configuring the underlying software. The asterisk is that they’re shaped by the needs and contributions of individuals. They’re a great path forward if the software you need has a module that supports your needs. If not, I don’t think developing or extending a module is a good place to start. (But it’s a great learning exercise once you’ve found your sea legs…)
  2. Cut out the middle-man. Take a config and either read the file into your Nix expression by path, or literally paste the contents into a string, write it into the store as a file, and link it into place.

I think community practice is still evolving here, but hopefully someone else will swat my hand if they feel like a clear best-practice has emerged. might be an interesting read in this regard.

1 Like

home-manager seems to work pretty well so far :slight_smile:
I like that there’s just 2-3 files I need to keep track of.

For me, the workflow that’s worked the best is to just group all of the packages that I want into a single nix attribute, then run nix-env -riA <that attr>. The -r flag in nix-env will atomically remove anything not included in the install, so it effectively removes the imperative package handling and keeps my top-level env clean.