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

155 lines
5 KiB
Markdown

# Nix + direnv Setup
This project uses [Nix flakes](https://nixos.wiki/wiki/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)
```bash
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:
```bash
nix --version
nix flake --help
```
### 2. Install direnv
```bash
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`):
```fish
direnv hook fish | source
```
**zsh** (`~/.zshrc`):
```zsh
eval "$(direnv hook zsh)"
```
### 3. Install nix-direnv
```bash
brew install nix-direnv
```
Then configure direnv to load it. Create (or append to) `~/.config/direnv/direnvrc`:
```bash
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`
```bash
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
```bash
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`:
```bash
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:
```bash
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:
```bash
nix flake update
```
To update just nixpkgs (e.g., to get a newer Erlang/Elixir):
```bash
nix flake lock --update-input nixpkgs
```
Commit the updated `flake.lock` so everyone gets the same versions.