Declarative package management on MacOs without home-manager or nix-darwin

Hi Nixos forum dwellers,

I’ve just started using Nix on MacOs and I am trying to configure a declarative package management setup.

Goal 1: user specific default packages

My goal is tho have a configuration that I can sync between multiple Macs (and preferably any other unix machines/VMs) and run some command to install packages listed in that configuration. As a result packages in the list should be available only for the current user via $PATH.

I see that people use home-manager and nix-darwin to configure a list of packages they want to be always installed. But nix-darwin installation is too invasive and I do not want to configure all the system settings with it; and home-manager is also way over the functionality I need.

While checking home-manager code I see that they use pkgs.buildEnv to provide a list of packages and then they run nix profile install or nix-env -i to install packages to the user environment. So I came up with this flake:

  description = "Nick's flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  outputs = { self, nixpkgs }: {
    packages."aarch64-darwin".default = 
        pkgs = nixpkgs.legacyPackages."aarch64-darwin";
        pkgs.buildEnv {
        name = "global-packages";
        paths = with pkgs; [

It is synced to ~/.config/nixpkgs/flake.nix so I can add more packages into flake on one computer and go to another computer and get the same config.

I run nix profile install . (while reading more about nix-env -i it seems like it’s almost deprecated) in this directory and it installs my packages and makes them available by adding links to ~/.nix-profile

Is this The Proper way to set up declarative package management considering Nix approaches?

Goal 2: system wide default packages

Do the same as goal 1 but instead of doing user specific I want to make it system wide so that all users on a machine/VM get the same set of available packages by default.

Is there a way to have a similar flake for system wide configuration and to run some command to install it to profiles of other users? Or, Is it possible to add those binaries to /nix/var/nix/profiles/default/bin (already in $PATH) where all nix executables reside?

User’s in this case should not be required to do anything to bring those packages into their environment. (I have multi-user Nix installation via GUI installer)

In NixOs it seems like /etc/nixos/configuration.nix file handle what I am trying to achieve here but on MacOs I don’t see such file. Will it work if I create it?

I see that nix-darwin is used for system wide packages via environment.systemPackages option which it’s also passing to pkgs.buildEnv

Additional Questions

  1. While looking at flake.lock I see it locks version of the input (nixos/nixpkgs/nixos-unstable) while versions of packages in pkgs.buildEnv aren’t locked. Does that mean when I run nix profile install . it will always look for the latest version of the package coming from locked version of nixos/nixpkgs/nixos-unstable?
  2. I also see that there is manifest.json concept described here manifest.json - Nix Reference Manual that kind of solves my problem in Goal 1, what is a more preferable way manifest.json or flake.nix?
  3. How do I list packages and their versions installed via pkgs.buildEnv?

Have you looked at flakey-profile? It is mostly targeted at Goal 1, but to my understanding can operating on arbitrary profiles as well

Thanks for the suggestion.

I checked it out and it seems like it’s similar to my current approach since it relies on .buildEnv as well and it just provides an interface to run nix-env commands.

In my approach I would run nix profile install . or some nix-env cmd directly while in flakey-profile case I would run nix run .#profile.switch.

Also nix profile doesn’t support switching between multiple profile which I do not require (flakey-profile uses nix-env thus it supports switching)

After some digging and checking out home-manager turned out that home-manager is fully suitable and easy to manage. Since I do not need to manage dotfiles with it I decided to enhance my approach by @iFreilicht’s flake.nix for default packages example and come up with this:

# To install packages to the user environment, run:
#   nix profile install $HOME/.config/nixpkgs
# Or if you symlinked the flake to ~/.config/nixpkgs, you can run:
#   nix profile install "$(readlink -f $HOME/.config/nixpkgs)"
# To update the environment after changing this file or inputs, run:
#   nix profile upgrade ".*"

  description = "Default user environment packages";
  inputs = {
    nixpkgs.url = "github:NixOs/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  outputs = { self, nixpkgs, flake-utils }@inputs:
    (flake-utils.lib.eachDefaultSystem (system:
        pkgs = nixpkgs.legacyPackages.${system};
        defaultPackages = {
          inherit (pkgs)
          # Basic terminal tools
            gnused # gnused on all platforms
            direnv # automatically switch environments in development directories
            # Utilities
            bat # colorized cat
            # Development
            # Nix
            nixfmt-classic # format nix files
            nixpkgs-fmt # format nixpkgs files
        linuxPackages = with pkgs; { inherit git; };
        darwinPackages = with pkgs; { inherit ; };
        systemPackages = defaultPackages
          // (if (pkgs.lib.strings.hasInfix "linux" system) then
            { }) // (if (pkgs.lib.strings.hasInfix "darwin" system) then
              { });
        packageListString = pkgs.lib.concatMapStringsSep "\n" (x: "${x}")
          (builtins.attrValues systemPackages);
      in {
        packages.default = pkgs.buildEnv {
          name = "nick-default-packages";
          paths = builtins.attrValues systemPackages;
      })) // {
        # nixos config here

So now I am continuing looking for solution for Goal 2.

Turns out that there is no easy ways except nix-darwin to do system white configuration of packages.