Nix Quick Install

  1. Install Nix using the .pkg from Determinate Systems.

  2. Create a flake.nix file in the project's root directory (see below).

  3. Activate the development environment with nix-develop.

  4. Update dependencies with nix flake update as needed.

Direnv & Nix-Direnv Quick Install

  # Install Direnv
  brew install direnv

  # Install nix-direnv
  nix profile add nixpkgs#nix-direnv

  # Configure direnv to use nix-direnv
  mkdir -p ~/.config/direnv
  echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' >> ~/.config/direnv/direnvrc

  # Modify shell config:
  # For Fish, add this to ~/.config/fish/config.fish
  direnv hook fish | source

  # For Zsh, add this to ~/.zshrc
  eval "$(direnv hook zsh)"

  # Reload the shell config

What's Nix?

Nix is a declarative package manager. You declare your project's dependencies in a flake.nix file, and Nix builds that environment with nix develop. Unlike the chaos of "works on my machine" development, Nix guarantees everyone gets the same environment. No more version conflicts, no more missing dependencies, no more "did you remember to install X?"

Nix pairs nicely with nix-direnv. When you switch directories, your environment switches with you. It's honestly kind of magical.

The name Nix is overloaded: it's an OS, a programming language, and a package manager. Over the years, there have been multiple ways to set things up.

This method installs the Nix package manager and uses a flake.nix file in each project's root directory. It also uses nix-direnv to automatically run nix develop.

Setting Up Nix

Download the .pkg installer from Determinate Systems. This is the recommended way to install Nix on macOS because it survives system upgrades. The installer handles all the setup for you - no manual configuration needed.

Setting Up Direnv

Direnv automatically loads and unloads your environment when you cd into and out of project directories. Combined with nix-direnv, it caches your Nix environments so they load instantly instead of rebuilding every time.

First, install direnv with Homebrew:

brew install direnv

Then install nix-direnv and configure it:

nix profile add nixpkgs#nix-direnv
mkdir -p ~/.config/direnv
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' >> ~/.config/direnv/direnvrc

Hook direnv into your shell. For Fish:

echo 'direnv hook fish | source' >> ~/.config/fish/config.fish
source ~/.config/fish/config.fish

For Zsh, add to ~/.zshrc:

echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
source ~/.zshrc

Creating A Project

First, navigate to your project directory, or create a new one:

mkdir ~/my-project
cd ~/my-project

Creating A Flake

Flakes are the modern way to configure Nix projects. They're self-contained files that explicitly declare all dependencies and lock them to specific versions, making your environment completely reproducible across machines and time.

Create a flake.nix file in your project's root directory using your favorite text editor. Here's what the Rust development environment for this blog looks like:

A flake.nix file has three main parts: a description, inputs, and outputs:

{
  description = "Rust development environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    rust-overlay.url = "github:oxalica/rust-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, rust-overlay, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        overlays = [ (import rust-overlay) ];
        pkgs = import nixpkgs {
          inherit system overlays;
        };
      in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [
            # Rust toolchain with cross-compilation target
            (rust-bin.stable.latest.default.override {
              targets = [ "x86_64-unknown-linux-gnu" ];
            })

            # Rust development tools
            cargo-nextest
            cargo-watch
            just

            # Cross-compilation tools
            zig
            cargo-zigbuild
          ] ++ lib.optionals stdenv.isDarwin [
            # macOS SDK (provides all system frameworks)
            apple-sdk
          ];

          # Environment variables
          RUST_BACKTRACE = "1";
        };
      }
    );
}

Inputs

Inputs are other flakes your environment depends on:

  • nixpkgs - the main package repository, like a giant catalog of software

  • rust-overlay - provides up-to-date Rust toolchains

  • flake-utils - helper functions to reduce boilerplate

Outputs

Outputs define what your flake provides. This one creates a development shell with devShells.default. The buildInputs list declares exactly what tools you need:

The RUST_BACKTRACE environment variable gets set automatically when you enter the environment.

Activating Your Flake

Now that you have a flake.nix file, tell direnv to use it. In your project's root directory:

echo "use flake" > .envrc
direnv allow

The direnv allow command is a security feature - it prevents random directories from automatically running code on your machine. You have to explicitly trust each .envrc file.

After the first activation, Nix creates a flake.lock file that pins exact versions of all dependencies. Commit both flake.nix and flake.lock to version control so everyone gets identical environments.

Updating Dependencies

The flake.lock file pins your dependencies to specific versions. When you want to upgrade to the latest versions, use nix flake update:

nix flake update

This updates all inputs (nixpkgs, rust-overlay, flake-utils) to their latest versions and regenerates flake.lock.

To update just one input:

nix flake update nixpkgs

After updating, nix-direnv automatically rebuilds your environment the next time you cd into the directory. Check what changed with git diff flake.lock. If something breaks, revert with git restore flake.lock.

Does It Actually Work?

When you cd into a directory with a flake.nix and .envrc, nix-direnv builds this environment and adds all those tools to your PATH. When you leave, it removes them. Same project, same dependencies, every time.

Let's test it. Start from your home directory:

cd ~                     # Start from home
cd ~/my-project          # Should see "direnv: loading..." and environment activates
which cargo              # Shows Nix cargo
cd ~                     # Environment unloads
cd ~/my-project          # Loads instantly (cached!)

If you see the environment loading and unloading as you navigate, you're good to go. The second time you enter the directory should be nearly instant thanks to nix-direnv caching.