`nix-shell --pure` nix-command equivalent

TL:DR: Is there nix-shell --pure equivalent in nix-commands (i.e nix develop or nix shell)?

Use-case: Declarative description of developer environment (and most of it’s setup) via nix derivation.

For minimal example:

$  cat /etc/os-release
BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues"
BUILD_ID="22.05.20221030.26eb67a"
DOCUMENTATION_URL="https://nixos.org/learn.html"
HOME_URL="https://nixos.org/"
ID=nixos
LOGO="nix-snowflake"
NAME=NixOS
PRETTY_NAME="NixOS 22.05 (Quokka)"
SUPPORT_URL="https://nixos.org/community.html"
VERSION="22.05 (Quokka)"
VERSION_CODENAME=quokka
VERSION_ID="22.05"
$ nix --version
nix (Nix) 2.8.1
$ echo $PATH
/run/wrappers/bin:/home/<user_name>/.nix-profile/bin:/etc/profiles/per-user/<user_name>/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin
# dev.nix
{ nixpkgs ? import <nixpkgs> {} }:

nixpkgs.mkShell {
  name = "dev-shell";
  buildInput = with nixpkgs; [
    hello
  ];
  shellHook = ''
    echo "test";
  '';
}

Simplified usage transcript:

$ rg --version
ripgrep 13.0.0
$ hello
The program 'hello' is not in your PATH.
$ nix-shell --pure ./dev.nix
'test'
$ hello
Hello, world!
$ rg --version
The program 'rg' is not in your PATH.
$ echo $PATH
echo $PATH
/nix/store/pa9416c0x5qa4r2z3zq701qg8gyr0c73-bash-interactive-5.1-p16/bin:/nix/store/n1jgqr8xzjz9shn3ads5x07p8lqn5rqk-patchelf-0.14.5/bin:/nix/store/r7r10qvsqlnvbzjkjinvscjlahqbxifl-gcc-wrapper-11.3.0/bin:/nix/store/d7q0qfm12hb59wj63wyjs0hrdhmmapfz-gcc-11.3.0/bin:/nix/store/vkwlwmagq5i0if2f60ywrg2gxjf5xr9i-glibc-2.34-210-bin/bin:/nix/store/47n5hzqpahs7yv84ia6cxp3jg9ca8r86-coreutils-9.0/bin:/nix/store/10h6ymfb28wx6x8amj82h2sgw3ixrsb2-binutils-wrapper-2.38/bin:/nix/store/2b99rpx8zwdjjqkrvk7kqgn9mxhiidjy-binutils-2.38/bin:/nix/store/47n5hzqpahs7yv84ia6cxp3jg9ca8r86-coreutils-9.0/bin:/nix/store/6ib6hn9fq8mgkdq2nq5f7kz050p49rp2-findutils-4.9.0/bin:/nix/store/685c5dr4agkf7vx8ya7f1r9rd9qwg2ri-diffutils-3.8/bin:/nix/store/sppjn85p06m1il70kd05drg1j26cjxd3-gnused-4.8/bin:/nix/store/49vp3yp54fqliy7k8gvxsybd50l9a82f-gnugrep-3.7/bin:/nix/store/fr7vrxblkj327ypn3vhjwfhf19lddqqd-gawk-5.1.1/bin:/nix/store/5p3qyadsv163m7zvqssiw80zh6xfv2jv-gnutar-1.34/bin:/nix/store/2bwqikh67y1231ccb71gjfrggwjw066q-gzip-1.12/bin:/nix/store/wjf2554ffvap47vanabh9lk0dmj1q295-bzip2-1.0.6.0.2-bin/bin:/nix/store/2hvj24gaq4y32cyf0jp9sj01y00k7czy-gnumake-4.3/bin:/nix/store/0d3wgx8x6dxdb2cpnq105z23hah07z7l-bash-5.1-p16/bin:/nix/store/wa31fy0bgmq7p2gcvh5xyrr7m2v8i3s2-patch-2.7.6/bin:/nix/store/dr5shmim604rh50mmihwfic80k0wa3k0-xz-5.2.5-bin/bin

nix-shell --pure allows me not only to get rid of most of annoying environment variables, but also ensure that working environment (i.e. $PATH) is not populated with whatever user has in his default environment. I do not want binaries being propagated from outside of the nix derivation.

The newer versions of nix have nix develop and nix shell commands, which completely do not meet my requirements and what nix-shell --pure was doing.

For example:

# flake.nix
{
  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.05";
  };
 outputs = { self, nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system: {
      "nixpkgs" = import nixpkgs { inherit system; };
      "devShell" = import ./dev.nix { nixpkgs = import nixpkgs { inherit system; }; };
    });
}

Simplified usage transcript:

$ rg --version
ripgrep 13.0.0
$ hello
The program 'hello' is not in your PATH.
$ nix develop -i
'test'
$ hello
Hello, world!
$ rg --version
ripgrep 13.0.0 #SIC! It is still in the $PATH
$ echo $PATH
echo $PATH
/nix/store/n845kl3915ri14zlkqq7wf3n3xabmgal-patchelf-0.14.5/bin:/nix/store/ykcrnkiicqg1pwls9kgnmf0hd9qjqp4x-gcc-wrapper-11.3.0/bin:/nix/store/65v2c245h5qa9mpc7dxhqkfjinl6phx0-gcc-11.3.0/bin:/nix/store/rvgp96bwfmgz4b163flszfaygmhx8wl4-glibc-2.34-210-bin/bin:/nix/store/qarssrazji0q9xp80xg8shsm2crckfr0-coreutils-9.0/bin:/nix/store/n5ypzvl7dijcg24isyngvw8fx0ri6hff-binutils-wrapper-2.38/bin:/nix/store/0q9hm42fapihzj1d64nxqmbml7fpb2d6-binutils-2.38/bin:/nix/store/qarssrazji0q9xp80xg8shsm2crckfr0-coreutils-9.0/bin:/nix/store/j25abvpcbappy74w23l8lfcz7gkrsjhy-findutils-4.9.0/bin:/nix/store/2kz1pihzg1jfif46mdm917xmj6r9xyz6-diffutils-3.8/bin:/nix/store/yyg26p5j2mrjwpkbk1djh4nxlsm2p4rw-gnused-4.8/bin:/nix/store/p7pqs5c4zfc4y977p626zch11msmmpj8-gnugrep-3.7/bin:/nix/store/pmh9q9k0g9s189v0iqrxpdp8j1g77gmd-gawk-5.1.1/bin:/nix/store/3fw5n1g3bb925hfll7fj7x94bd0q6k0r-gnutar-1.34/bin:/nix/store/nrvhb0yvawiqgrwbmbfmhjrmy934hhs5-gzip-1.12/bin:/nix/store/n7208v30hf3z4sz6127947vmzpfl46nb-bzip2-1.0.6.0.2-bin/bin:/nix/store/c5myz1zs9bsaq3y4s0rcxgkzb11irwra-gnumake-4.3/bin:/nix/store/9zm6br2ri10a0b71dll2wrim5bnhg6b6-bash-5.1-p16/bin:/nix/store/8636bkd1gg5s8675ipb9wf98wrc5mdpw-patch-2.7.6/bin:/nix/store/5ywhaxrasrp3pj72dnyy40mimx13fi0p-xz-5.2.5-bin/bin:/run/wrappers/bin:/.nix-profile/bin:/etc/profiles/per-user//bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin

I have been unable to figure out how to retain the behaviour of the nix-shell --pure in new nix-commands - is it even possible?
I know that I can use flake-compact to run nix-shell --pure on flake, but I feel like it is a huge omission to forgo this capability in nix-commands.

Edit #1: Added list of OS, nix versions and $PATH content

2 Likes

This doesn’t match my results:

tlater ~ $ rg --version
ripgrep 13.0.0
-SIMD -AVX (compiled)
+SIMD +AVX (runtime)
tlater ~ $ nix develop -i nixpkgs#python3
bash-5.1$ rg --version
rg: command not found

I’ve confirmed that with actual flake.nixes as well.

Is it perhaps bash loading some environment variables when it’s starting for you? Maybe bash needs to be invoked with --norc --noprofile or such. What does your $PATH actually contain?

In this particular case, I noticed that when you use NixOS and you install e.g. rg globally (environment.systemPackages) it will be still available in your dev shell (checked with silver-searcher :slight_smile: )

$ nix develop -i
test
bash-5.1$ ag

Usage: ag [FILE-TYPE] [OPTIONS] PATTERN [PATH]
...

And I can confirm that everything from environment.systemPackages leaks to the dev shell.

It does not for me. When I start a nix develop -i then /run/current-system/sw/bin will not be in PATH anymore.

$ nix develop -i nixpkgs\#hello -c printenv PATH | rg current-system
$ echo $?
1

This gets interesting :slight_smile: (/run/current-system/sw/bin in the end when run sequentially…):

$ nix develop -i nixpkgs\#hello
bash-5.1$ printenv PATH
/nix/store/7bdilcs9ygs5dwynn798f26060fggp7g-patchelf-0.15.0/bin:/nix/store/2bg235ac1s6cpxwr1pjagn6bj8q2425l-gcc-wrapper-11.3.0/bin:/nix/store/8xmmwmb6r6hc6f3cdh64mbi138p96vpg-gcc-11.3.0/bin:/nix/store/4xynblhic6sq4paq2ra4ql535yyvracs-glibc-2.35-163-bin/bin:/nix/store/xq4g38m5ppg78z7bzfxyy2s8ii693k74-coreutils-9.1/bin:/nix/store/bj1i5gr4l6p5pkcfyp48mni85d2ydz71-binutils-wrapper-2.39/bin:/nix/store/pmgnlnbygb95s4zc8sqhknz9sdz934pk-binutils-2.39/bin:/nix/store/xq4g38m5ppg78z7bzfxyy2s8ii693k74-coreutils-9.1/bin:/nix/store/1s3ifgirxw3x89b64a6sg6pxydgz8cr1-findutils-4.9.0/bin:/nix/store/1wf98fslhfph028xhkxdbp3q8y8rhr02-diffutils-3.8/bin:/nix/store/jvhh6i1647pn2f1kay9xk8b7i8acdjh1-gnused-4.8/bin:/nix/store/sqypvzmrsl8hil564xfk8n5w51gq47h0-gnugrep-3.7/bin:/nix/store/pq5rfrihdhhj6aqjh9ix8dycgp55ij8p-gawk-5.1.1/bin:/nix/store/m2jgkyyr88nmxil78c4jx1sh4xzw7y1v-gnutar-1.34/bin:/nix/store/acslph124hwcca3dkccd7ybl7wmc5j3p-gzip-1.12/bin:/nix/store/van5xy9axychdx2q7b4mv9apqhxfng3b-bzip2-1.0.8-bin/bin:/nix/store/25nm17pd42qmyczrkh80ya2hh9jvgp0g-gnumake-4.3/bin:/nix/store/y0g1mvsr6vygr61f9znljik9kl0x0inc-bash-5.1-p16/bin:/nix/store/xjk7y6cbxn8g54b0fh67rs1h69g62hnl-patch-2.7.6/bin:/nix/store/71lr8dndinlkiyjmdcgi8kq2xhjdchn6-xz-5.2.7-bin/bin:/nix/store/dxz4jr6gj2day09ribxiai0406p64nm0-file-5.43/bin:/run/wrappers/bin:/.nix-profile/bin:/etc/profiles/per-user//bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin
bash-5.1$ exit
$ nix develop -i nixpkgs\#hello -c printenv PATH
/nix/store/7bdilcs9ygs5dwynn798f26060fggp7g-patchelf-0.15.0/bin:/nix/store/2bg235ac1s6cpxwr1pjagn6bj8q2425l-gcc-wrapper-11.3.0/bin:/nix/store/8xmmwmb6r6hc6f3cdh64mbi138p96vpg-gcc-11.3.0/bin:/nix/store/4xynblhic6sq4paq2ra4ql535yyvracs-glibc-2.35-163-bin/bin:/nix/store/xq4g38m5ppg78z7bzfxyy2s8ii693k74-coreutils-9.1/bin:/nix/store/bj1i5gr4l6p5pkcfyp48mni85d2ydz71-binutils-wrapper-2.39/bin:/nix/store/pmgnlnbygb95s4zc8sqhknz9sdz934pk-binutils-2.39/bin:/nix/store/xq4g38m5ppg78z7bzfxyy2s8ii693k74-coreutils-9.1/bin:/nix/store/1s3ifgirxw3x89b64a6sg6pxydgz8cr1-findutils-4.9.0/bin:/nix/store/1wf98fslhfph028xhkxdbp3q8y8rhr02-diffutils-3.8/bin:/nix/store/jvhh6i1647pn2f1kay9xk8b7i8acdjh1-gnused-4.8/bin:/nix/store/sqypvzmrsl8hil564xfk8n5w51gq47h0-gnugrep-3.7/bin:/nix/store/pq5rfrihdhhj6aqjh9ix8dycgp55ij8p-gawk-5.1.1/bin:/nix/store/m2jgkyyr88nmxil78c4jx1sh4xzw7y1v-gnutar-1.34/bin:/nix/store/acslph124hwcca3dkccd7ybl7wmc5j3p-gzip-1.12/bin:/nix/store/van5xy9axychdx2q7b4mv9apqhxfng3b-bzip2-1.0.8-bin/bin:/nix/store/25nm17pd42qmyczrkh80ya2hh9jvgp0g-gnumake-4.3/bin:/nix/store/y0g1mvsr6vygr61f9znljik9kl0x0inc-bash-5.1-p16/bin:/nix/store/xjk7y6cbxn8g54b0fh67rs1h69g62hnl-patch-2.7.6/bin:/nix/store/71lr8dndinlkiyjmdcgi8kq2xhjdchn6-xz-5.2.7-bin/bin:/nix/store/dxz4jr6gj2day09ribxiai0406p64nm0-file-5.43/bin:/no-such-path

Oooof…
Yes, when not using -c I can reproduce…

And after I delete /etc/bashrc I can not reproduce anymore. So it is the impure fact that bash reads in its own configuration files.

3 Likes

Hi all,
Thanks for your pointers - I have updated the post to include my OS, nix version and $PATH.

Precisely as @kczulko has noticed, the differences seem to be with default profiles being appended at the end (i.e. :/.nix-profile/bin:/etc/profiles/per-user//bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin).

My follow-up question would then be: is that expected behaviour? Imho nix-shell --pure works better by default and I would consider contributing upstream to fix the nix develop

I think they reason why it works for nix-shell is, that it uses the bash derivation provided by the stdenv, while nix develop replaces that with bashInteractive for convinience reasons.

The problem with the former is that the prompt and some other niceties for humans do not work (correctly), as thats a stripped down bash optimized for running scripts and keeping the closure smaller.

2 Likes

weirdly enough if i do nix shell -i -c bash i get a strange incomplete PATH of “/.local/bin:/run/wrappers/bin:/.nix-profile/bin:/etc/profiles/per-user//bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin” but yes i have tested and using bash --norc --noprofile works to get a clean PATH

@AleksanderGondek
My colleague showed me this post
As a follow up - this is what works in my case (zsh):
nix develop -i -c $SHELL

2 Likes

I want to bump this thread as I too also think that it should be possible (or dare I say the default?) for running nix develop in a “pure” mode as well.

This is precisely why I keep a flake.nix and a shell.nix file with matching packages (except in the shell.nix file I additionally include nixpkgs.cacert to the packages in order for SSL to work properly with git repos and the npm registry when running in a nix-shell --pure.).

When I am developing, I usually just jump in with nix develop; which gives me access to the human niceties I have globally installed like powerline-go, but I still jump into my environment with nix-shell --pure every now and then to ensure that the project still builds fine without any global packages, i.e. any packages not defined in the repositories nix file.

It does; the problem is the detail of interactive bash vs non-interactive bash. Nix can’t really do much about bash’ behavior.

Ultimately for most it’s less surprising the way it is implemented now - you don’t end up with broken completions, color escape sequences not processing or newlines not printing. And it enables your powerline-go, which would not work in a non-interactive shell.

The tradeoff is that some global packages end up in the shell anyway because they’re added to $PATH through /etc/bashrc.

If you want to ensure a pure environment, use -c to get into a non-interactive shell that won’t run /etc/bashrc - this will break most interactive bash behavior, but give a better representation of a clean environment. I’d argue that if something should change in nix, it’s that a flag should be added to shortcut doing just that so you don’t need to understand the underlying behavior of the shell.

But then, the exact behavior of nix develop and nix shell is clearly not entirely thought out yet.

2 Likes

If I understand correctly, it is possible to get an absolutely pure environment using a command like nix develop -i -c bash --norc, but this also means you’ll not get all the nice features of an interactive shell. Is this correct?

From what I found out, nix develop -i -c bash --norc gives me a pretty bare bones shell, which is also usable. The PATH looks like this:

bash-5.2$ tr ':' '\n' <<< $PATH
/nix/store/cb4mxc1mnl97590zbbhclgvqn4iqqap7-gcc-wrapper-12.3.0/bin
/nix/store/d3bkxbf2m35s5023xa1pis4vj6c7p9ac-gcc-12.3.0/bin
/nix/store/9y54hdss3sjj9a7d9fwn3d704557yja6-glibc-2.38-44-bin/bin
/nix/store/26zdl4pyw5qazppj8if5lm8bjzxlc07l-coreutils-9.3/bin
/nix/store/959n1fikgn87ivc818r93qg0rh8jr51h-binutils-wrapper-2.40/bin
/nix/store/bs8irpchp9yrp2azs3arm0b88mrsip6d-binutils-2.40/bin
/nix/store/7h04wbizdnd94piryjbdsn8xw0q3karq-rust-default-1.79.0-nightly-2024-04-16/bin
...
/no-such-path

Trying to use zsh instead of bash on the other hand, behaves very weird.
I tried replaceing bash with zsh (I also added zsh to the flake.nix) like this nix develop -i -c zsh --no-rcs, which gave me a very different result.
First of all the shell does not recognize all keys (backspace inserts a space instead of deleting a character and so on). Secondly the path looks like this:

nuc% tr ':' '\n' <<< $PATH                                                                                  
/run/wrappers/bin
/home/<username>/.nix-profile/bin
/nix/profile/bin
/home/<username>/.local/state/nix/profile/bin
/etc/profiles/per-user//bin
/nix/var/nix/profiles/default/bin
/run/current-system/sw/bin

I first thought this might be due to some .zshrc file that gets loaded (even though I specified the --no-rcs option), but I also tried out fish shell (which I installed freshly) and it gave me a similar result (PATH is the same, but the shell behaves a little different to zsh on some keys).

Can anyone explain why the path differs in these cases and why I cannot replace bash like that?

1 Like