My first steps with Nix on Mac OSX as Homebrew replacement

I finally had some time to play around with Nix - the immutable package manager and build system.  This was on my agenda since a long time, but I finally took the plunge on my M1 OSX system. I by no means understand Nix fully yet, but making progress and it is usable to me already.

Getting Started

I started by reading all of zero-to-nix.com - a great website which explains the first baby steps. For installing, I used the unofficial installer DeterminateSystems/nix-installer for reasons explained on the website, which ran through smoothly.

After doing the tutorials at zero-to-nix.com, I wondered how to continue. After all, I had two main goals:

  • have a reproducible global dev tool setup (basically a replacement for Homebrew)
  • have reproducible dev environments (basically replacing docker)

For the second use case, reproducible dev environments, I quickly settled on cachix/devenv. I also checked out jetpack-io/devbox which does fundamentally the same thing; but which hides the Nix files completely underneath - so that was too restrictive for my tastes.

When reading about the ecosystem, I saw there are lots of patterns how one could use Nix; and this is in flux right now, as the community irons out flakes (which many people seem to be using already though). So I for now decided on the following:

  • Embrace Nix Flakes: I'll only use nix flakes, especially after reading this blog post.
  • Only use the nix tool, instead of the older nix-env etc tools. It seems the new world and the old world sometimes are incompatible, and you need to decide which way to go consistently.
  • Only go declarative: I will never install a package by typing a CLI command, but always editing some configfile and running a "converge" script.

I have read that flakes and nix are still considered alpha; but from my perspective right now it feels 95% done - and maybe other projects would have called this a finished product by now. So I feel good about using this.

Global Dev Setup

For installing tools globally, I've read about nix profiles, but I was unsure about how to use them with Flakes. I then stumbled over this forum post, which is the way I am doing it now:

  1. I created a folder in ~/nix-sebastian (does not matter) containing my global Nix configuration.
  2. In this folder, I placed a flake.nix (content see below), effectively making this folder a flake.
  3. Then, for initial installation, I ran nix profile install . (inside the folder). This registered the flake.nix as part of the global nix profile. You can see that nix profile list shows the profile. 
  4. To update, you need to run nix profile upgrade 0 (assuming you only have this single profile installed) - if you have multiple profiles installed, you need to use the number displayed at the front of the listing.

Pitfall: you are not allowed to re-run nix profile install ., as this will install the profile a second time - and this will lead to conflicts. This is what I stumbled over at first :)

flake.nix skeleton

The following is the skeleton file I am using for global installation. To understand the syntax, this post about language basics and this post about functions have been helpful for me.

# flake.nix skeleton: { inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; }; outputs = { self, nixpkgs }: { packages."aarch64-darwin".default = let pkgs = nixpkgs.legacyPackages."aarch64-darwin"; in pkgs.buildEnv { name = "home-packages"; paths = with pkgs; [ # general tools git # ... add your tools here ]; }; }; }

Development Environments with devenv

I cannot say much about devenv.sh, except that it works as intended :) The docs are a bit sparse, but I found the devenv.nix file reference to be extremely helpful. It shows what you can all install and how you can customize the environment - I especially like the customizability of languages, and that the system supports services and scripts. As an example, for a Neos/Flow PHP application, I am using the following devenv.nix. It does not yet install all optimal PHP extensions, but it already sets up a database with permissions :)

In case you wonder, I used the "Advanced (declaratively with flakes)" installation method of devenv.sh described here.

# devenv.nix { pkgs, ... }: { languages.php.enable = true; languages.php.package = pkgs.php82; services.mysql = { enable = true; initialDatabases = [ { name = "neos"; } ]; ensureUsers = [ { name = "neos"; ensurePermissions = { "neos.*" = "ALL PRIVILEGES"; }; } ]; }; starship.enable = true; }

Bonus and Closing Thoughts

So far I like what I saw :) I only played around with this for a few hours, but I was already able to remove most of my homebrew packages and convert them to Nix. I am curious where I will end up :)

In case you are interested, below is my current global flake.nix file, with the following specials:

  • install devenv
  • install diverse language toolchains (Rust, php, deno, node, JDK, go)
  • install PHP with extensions
  • custom-compile a PHP extension

my current global flake.nix file

# global flake.nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; unstable.url = "nixpkgs/nixos-unstable"; devenv.url = "github:cachix/devenv/latest"; # rust, see https://github.com/nix-community/fenix#usage fenix = { url = "github:nix-community/fenix"; inputs.nixpkgs.follows = "unstable"; }; }; outputs = { self, nixpkgs, unstable, devenv, fenix }: { packages."aarch64-darwin".default = let pkgs = nixpkgs.legacyPackages."aarch64-darwin"; unstablePkgs = unstable.legacyPackages."aarch64-darwin"; # see https://github.com/Mic92/nixos-wiki-test/blob/196aa6c3463bf52128d3ffb07218d0999b2ca617/PHP.md?plain=1#L38 php-vips = pkgs.php81.buildPecl { pname = "vips"; version = "1.0.13"; sha256 = "TmVYQ+XugVDJJ8EIU9+g0qO5JLwkU+2PteWiqQ5ob48="; buildInputs = [ pkgs.vips pkgs.pkg-config ]; }; php81 = pkgs.php81.buildEnv { extensions = ({ enabled, all }: enabled ++ (with all; [ xdebug opcache redis php-vips ])); extraConfig = "memory_limit = 2G"; }; in pkgs.buildEnv { name = "home-packages"; paths = with pkgs; [ # general tools git bitwarden-cli ffmpeg gnupg curl wget jq gnused ripgrep tmux pandoc # dev tools devenv.packages.aarch64-darwin.devenv mitmproxy cfssl dive graphviz lnav # log file nav watchman vector kubectl httpie certstrap lima caddy mutagen mutagen-compose upx age dbmate gomplate jwt-cli ipcalc ncdu inetutils # telnet # infrastructure ansible k3d k9s natscli nats-server redis trivy # programming environments deno nodejs yarn php81 symfony-cli fenix.packages."aarch64-darwin".minimal.toolchain # rust jdk17 # go go unstablePkgs.goreleaser protobuf # TODO remove protoc-gen-go # TODO remove protoc-gen-go-grpc # TODO remove golangci-lint # TODO remove buf # TODO remove gum # TODO REMOVE #mariadb #postgresql # to remove awscli google-cloud-sdk platformio arduino-cli openfortivpn hcloud pgcli # mobile dev # TODO Flutter does not work cocoapods fastlane ]; }; }; }