As a senior solutions architect working across multiple projects, I need a way to ensure each project has its own isolated development environment with the exact dependencies it requires. This is where the combination of direnv and Nix becomes incredibly powerful.
Direnv is an environment switcher that automatically loads and unloads environment variables when you enter or leave directories. It’s like having a .bashrc or .zshrc that’s specific to each project directory.
When combined with Nix, direnv provides:
Let me walk through how I set up my Ruby on Rails project (sports-edge) using this combination.
.envrc FileThe .envrc file is the trigger that tells direnv what to do:
use nix
That’s it! This simple directive tells direnv to use the Nix shell defined in shell.nix.
shell.nix FileHere’s the actual Nix shell configuration for my Rails project:
let
pkgs = import <nixpkgs> { system = "aarch64-darwin"; };
in
pkgs.mkShell {
buildInputs = with pkgs; [
ruby_3_4
gcc
gnumake
pkg-config
zlib
openssl
libyaml
gmp
readline
rustc
nodejs
nixd
postgresql
nixfmt
];
nativeBuildInputs = [ pkgs.pkg-config ];
shellHook = ''
set -e
# Ensure Apple Silicon native builds
export ARCHFLAGS="-arch arm64"
# Suppress RubyGems/Bundler constant warnings
export RUBYOPT="-W0"
# Isolate gems
export GEM_HOME="$PWD/.gem"
export GEM_PATH="$GEM_HOME"
export BUNDLE_PATH="$GEM_HOME"
export BUNDLE_BIN="$GEM_HOME/bin"
export BUNDLE_DISABLE_SHARED_GEMS=1
export PATH="$BUNDLE_BIN:$PATH"
# Disable documentation generation
mkdir -p "$PWD/.gem"
echo "gem: --no-document" > "$PWD/.gem/gemrc"
export GEMRC="$PWD/.gem/gemrc"
echo "Ruby version: $(ruby --version)"
if [ -f Gemfile ]; then
echo "Rails version: $(bundle exec rails --version 2>/dev/null || echo 'not in Gemfile')"
else
echo "Rails version: $(command -v rails >/dev/null 2>&1 && rails --version || echo 'Gemfile not found')"
fi
'';
}
The buildInputs section includes all the tools needed for Rails development:
The shellHook runs when the shell activates:
ARCHFLAGS="-arch arm64"RUBYOPT="-W0".gem/ directoryI also use .tool-versions for compatibility with other tools:
ruby 3.4.6
This works alongside the Nix setup, providing redundancy and compatibility with tools like asdf.
.envrc with use nixshell.nix with your dependenciesdirenv allow to approve the configuration# Enter project directory
cd sports-edge
# Direnv automatically activates:
# Ruby version: ruby 3.4.6p321 (2024-11-05 revision 31f3c7b7a9) [arm64-darwin23]
# Rails version: Rails 8.0.0
# Work normally
rails server
bundle exec rspec
cd ~/projects/another-project
# Direnv unloads sports-edge environment
# Direnv loads another-project environment
cd ~/projects/sports-edge
# Direnv unloads another-project environment
# Direnv loads sports-edge environment again
shell.nix works for all team membersgit clone and direnv allowshell.nix serves as living documentationYou can make dependencies conditional:
buildInputs = with pkgs; [
ruby_3_4
] ++ lib.optionals stdenv.isLinux [ linux-pam ]
++ lib.optionals stdenv.isDarwin [ darwin.apple_sdk.frameworks.Security ];
Create different shells for different environments:
# .envrc.development
use nix -p ruby_3_4 nodejs postgresql
# .envrc.production
use nix -p ruby_3_4 nodejs_20
The setup works seamlessly with:
direnv: error .envrc is blocked
Solution: direnv allow
error: build of '/nix/store/...' failed
Solution: nix-store --verify --check-contents or nix-collect-garbage
Large dependency sets can slow down activation. Consider:
.envrc and shell.nix to version controlshell.nixThe combination of direnv and Nix has transformed how I manage development environments. It provides the isolation benefits of containers with the convenience of automatic activation. For anyone working across multiple projects or teams, this approach eliminates entire classes of environment-related problems and makes development more predictable and enjoyable.
The initial investment in setting up shell.nix files pays dividends in reduced setup time, fewer environment bugs, and easier onboarding for new team members. It’s a pattern I now use for all my projects, from simple scripts to complex Rails applications.
Have questions about using direnv with Nix, or want to share your own environment management patterns? Feel free to reach out or open an issue on the nix-config repository.
| Contact: jeanre.swanepoel@gmail.com | +27 68 618 3487 |