I’ve been using nix-shell and nix develop for quite some time, mainly to create reproducible development environments in projects (mostly on the JVM) that don’t use Nix otherwise.
Until recently, I didn’t pay much attention to what happens behind the scenes. But over time, I started noticing some unexpected behaviors: temporary files created in nix-specific places, a large number of environment variables (some with multiline values), and escaping issues in VS Code’s integrated terminal (which was recently fixed in this PR).
These quirks made me curious about why these tools behave the way they do. I’ve since read through documentation, experimented with different setups (mkShell, devenv, devshell.nix), and compared how nix-shell and nix develop set up their environments. While I’ve found some explanations, several questions remain - mostly around the reasoning behind these design choices rather than their implementation details.
Hopefully someone can clarify a few of these questions - maybe even paving the way for better documentation or a blog post down the line.
Environment Differences between nix-shell and nix develop
While comparing nix-shell and nix develop, I noticed that they produce surprisingly different rc files and resulting environments. For example:
nix-shellunsets theTZvariable, whilenix developkeeps it.nix develophandlesXDG_DATA_DIRS, whichnix-shellignores.- Each exposes a different set of environment variables — for example,
NIX_BUILD_CORES,OLDPWD,systemand more in one case, versusexitHooks,failureHooksetc in the other. - Most of these environment variables aren’t documented, and I couldn’t find a comprehensive list.
Questions
- Was this divergence an intentional design change introduced with
nix develop? - What’s the expected contract or stability of these environment variables? Are they meant to be relied on, or can they change across Nix releases?
nix developfails when no value foroutputsis provided, whilenix-shelldoes not. Why mustoutputsbe present fornix developand why isn’t there a fallback for it?
Plain Derivations vs. stdenv-Based Shells
Most projects I’ve seen use mkShell (or mkShellNoCC) to define development environments. In contrast, devshell.nix creates a “naked” shell from a plain derivation, and devenv used to do the same before reverting. The main difference I’ve noticed between plain derivations and those based on stdenv is that the latter supports setup hooks.
Questions
- Both
nix-shellandnix developsource$stdenv/setup. This behavior is documented explicitly fornix-shell, but is only mentioned indirectly fornix develop. From what I understand,stdenvis primarily a concept defined innixpkgs, not something that belongs to Nix itself. Was this naming intentional to be “in sync” withnixpkgsor to suggest something broader? - Are setup hooks critical in developer shell environments? My projects mostly use the JVM where static/dynamic linking isn’t a major concern.
- What are the practical upsides and downsides of relying on setup hooks to prepare a devshell environment?
- Would it make sense for Nix to support a concept of minimal shells that provide only the bare essentials without additional environment variables? Or is this just bikeshedding or even harmful?
Finally, as far as I understand it, neither nix-shell nor nix develop were designed to be used to create general purpose developer shells.
- Are these the right Tools for such Developer Shells? Or should we build something similar (ideally in
nixpkgs) that solves this differently? Or does this already exists?
I realize that’s a lot of questions, but I’d really appreciate any insights - even partial ones.
If you’ve dug into these differences yourself, have historical context, or opinions on how developer environments should be handled in Nix, I’d love to hear it.