prop/nix.md
Graham McInitre a8ecee32b0 feat: add Nix flake dev environment with Erlang 29.0.3 + Elixir 1.20.2
- Add flake.nix + flake.lock pinning nixpkgs-unstable
- Add nix/shell.nix with PostgreSQL, Rust, Elixir LSP, pre-commit hooks
- Add shell.nix flake-compat shim for non-flake nix-shell users
- Bump Dockerfile to Elixir 1.20.2 / OTP 29.0.3
- Add nix.md with setup and troubleshooting docs
- Gitignore Nix runtime state and generated pre-commit config

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-15 12:57:10 -05:00

5 KiB

Nix + direnv Setup

This project uses Nix flakes to provide a reproducible dev environment that activates automatically when you cd into the project directory. No more "works on my machine" — every dev gets the same Elixir, Erlang, Rust, and PostgreSQL.

How It Works

  1. Nix flakes (flake.nix) declare every tool and library the project needs, pinned via flake.lock.
  2. direnv watches for .envrc and loads/unloads the environment as you enter/leave the directory.
  3. nix-direnv bridges the two, so use flake in .envrc triggers nix develop.

The result: cd into the repo and you're ready to run mix phx.server. cd out and your shell is clean. No global installs, no version conflicts, no manual setup.

File Layout

File Purpose
flake.nix Top-level flake: pins nixpkgs, Erlang/Elixir versions, defines dev shell
flake.lock Locked hashes for every dependency — reproducible across machines
nix/shell.nix Dev shell definition: packages, env vars, PostgreSQL service scripts, pre-commit hooks
shell.nix Flake-compat shim so nix-shell works without --extra-experimental-features flakes
.envrc Contains use flake (gitignored — create it yourself on each machine)

One-Time Setup

1. Install Nix (multi-user)

sh <(curl -L https://nixos.org/nix/install)

Follow the prompts. The multi-user install (--daemon) is recommended on macOS. When it finishes, open a new terminal so the Nix daemon is running.

Enable flakes. Add this to ~/.config/nix/nix.conf (create the file if needed):

experimental-features = nix-command flakes

Verify:

nix --version
nix flake --help

2. Install direnv

brew install direnv

Add the direnv hook to your shell's rc file. Running brew info direnv will print the exact line — pick the one for your shell:

fish (~/.config/fish/config.fish):

direnv hook fish | source

zsh (~/.zshrc):

eval "$(direnv hook zsh)"

3. Install nix-direnv

brew install nix-direnv

Then configure direnv to load it. Create (or append to) ~/.config/direnv/direnvrc:

mkdir -p ~/.config/direnv
cat > ~/.config/direnv/direnvrc << 'EOF'
source "$(brew --prefix nix-direnv)/share/nix-direnv/direnvrc"
EOF

If you installed nix-direnv via nix profile instead of Homebrew, the source line is:

source $HOME/.nix-profile/share/nix-direnv/direnvrc

4. Create .envrc

echo 'use flake' > .envrc

This is gitignored — each dev creates their own. You can add personal overrides (e.g., extra env vars) here.

5. Restart your shell

Open a new terminal tab or exec $SHELL so the direnv hook takes effect.

6. First time entering the project directory

cd ~/dev/prop
direnv allow   # one-time approval — thereafter it loads automatically

The first build may take several minutes while Nix downloads and compiles packages (Erlang, Elixir, Rust, PostgreSQL, etc.). Subsequent entries are instant — direnv caches the shell.

Daily Use

Once set up, just cd into the repo. The shell activates automatically:

  • PostgreSQL starts on port 5432 with prop_dev and prop_test databases created
  • start-services / stop-services commands are available to manually control PostgreSQL
  • Mix/Hex are sandboxed to .nix-mix / .nix-hex (doesn't pollute ~/.mix)
  • Pre-commit hooks (mix format, credo, nixfmt, cargo fmt, cargo clippy) install automatically
  • Services stop automatically when you cd out of the directory

Troubleshooting

direnv: error .envrc is blocked Run direnv allow in the project directory.

nix develop fails with "experimental Nix feature 'nix-command' is disabled" Add experimental-features = nix-command flakes to ~/.config/nix/nix.conf.

Empty shell, no Elixir available Run nix develop --command elixir --version to see build output. Check for syntax errors in flake.nix or nix/shell.nix:

nix flake check

Flake can't see new or modified files Nix flakes only see files tracked by git. If you added a new nix file, stage it first:

git add -N <file>

Nix build fails with a hash mismatch in shell.nix (flake-compat) The flake-compat tarball hash in shell.nix is stale. Update it to the actual hash shown in the error message.

direnv not triggering on cd Check your shell's rc file has the hook line from step 2. Run direnv status in the project directory.

I added a new package to nix/shell.nix — how do I reload? direnv watches flake.nix and flake.lock for changes and rebuilds automatically. If it doesn't pick up the change, run direnv reload.

Updating Dependencies

The flake pins everything via flake.lock. To update all inputs to their latest versions:

nix flake update

To update just nixpkgs (e.g., to get a newer Erlang/Elixir):

nix flake lock --update-input nixpkgs

Commit the updated flake.lock so everyone gets the same versions.