Normally our Unix systems organise the file system in a structure called the Filesystem Hierarchy Standard (FHS). Installing into an FHS has limitations, what would happen if we want to install, for example, two different versions of ruby at the same time? Typically this isn't possible without explicitly specifying a separate installation directory, if we just install to the usual place e.g. /usr/bin then we will just overwrite the previous ruby. So perhaps we would install one ruby into /usr/bin and another into /usr/local/bin, this is fine, but what about dependent libs? Assuming the two different versions of ruby do require different dependencies then we have potentially the same problem that the dependencies for the 1st ruby might overwrite the dependencies for the 2nd ruby.

Nix gets around this to some extent by not using FHS, instead nix installs all files into the nix store, which is usually located at /nix/store. All programs in a nix store are identified by their store path, which is uniquely generated for each distinct nix package. As a result of this, different versions of the same ruby no longer conflict because they are each assigned their own locations within the nix store.

To enable use of programs within the store, nix maintains an environment which is basically a mapping of FHS path -> nix store path, where the -> is a symlink. So for example, let's first install ruby 2.0 into our environment

nix@salo:~$ nix-env -f nixpkgs -iA pkgs.ruby_2_0
installing ‘ruby-2.0.0-p648’
these paths will be fetched (3.43 MiB download, 19.35 MiB unpacked):
  /nix/store/bxm4s71qdyh071ap5ywxc63aja62cbyc-gdbm-1.13
  /nix/store/d2ccapssrq683rj0fr7d7nb3ichxvlsy-ruby-2.0.0-p648
  /nix/store/h85k47l9zpwwxdsn9kkjmqw8pnfnrwmm-libffi-3.2.1
  /nix/store/zj8cjx71sqvv46sxfggjpdzqz6nss047-libyaml-0.1.7
fetching path ‘/nix/store/bxm4s71qdyh071ap5ywxc63aja62cbyc-gdbm-1.13’...
....
building path(s) ‘/nix/store/j649f78ha04mi1vykz601b00ml3qlr9q-user-environment’
created 419 symlinks in user environment

we can see the symlink that was just created to our ruby2.0 in the store,

nix@salo:~$ ls -l $(which irb)
lrwxrwxrwx 1 nix nix 67 Jan  1  1970 /home/nix/.nix-profile/bin/irb -> /nix/store/d2ccapssrq683rj0fr7d7nb3ichxvlsy-ruby-2.0.0-p648/bin/irb

nix@salo:~$ irb
irb(main):001:0> puts RUBY_VERSION
2.0.0

as you can see we're only able to execute the interactive ruby prompt irb because it's symlinked into our environment which is, of course, on the $PATH,

nix@salo:~$ echo $PATH
/home/nix/.nix-profile/bin:/home/nix/.nix-profile/sbin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

to prove the point about multiple versions on the same system let's swap ruby 2.0 for ruby 2.4

nix@salo:~$ nix-env -f nixpkgs -iA pkgs.ruby_2_4
replacing old ‘ruby-2.0.0-p648’
installing ‘ruby-2.4.1’
these paths will be fetched (3.13 MiB download, 15.32 MiB unpacked):
  /nix/store/48xrfkanmx5sshqj1364k2dw25xr4znj-ruby-2.4.1
fetching path ‘/nix/store/48xrfkanmx5sshqj1364k2dw25xr4znj-ruby-2.4.1’...
...
*** Downloading ‘https://cache.nixos.org/nar/00hh9w9nvlbinya1i9j0v7v89pw3zzlrfqps72441k7p2n8zq7d3.nar.    xz’ to ‘/nix/store/48xrfkanmx5sshqj1364k2dw25xr4znj-ruby-2.4.1’...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 3205k  100 3205k    0     0   114k      0  0:00:27  0:00:27 --:--:--  125k

building path(s) ‘/nix/store/7b2mmk2ffmy1c2bxq7r6y9cn6r0nwn8s-user-environment’
created 415 symlinks in user environment

nix@salo:~$ ls -l $(which irb)
lrwxrwxrwx 1 nix nix 62 Jan  1  1970 /home/nix/.nix-profile/bin/irb -> /nix/store/48xrfkanmx5sshqj1364k2dw25xr4znj-ruby-2.4.1/bin/irb

nix@salo:~$ irb
irb(main):001:0> puts RUBY_VERSION
2.4.1

You may be wondering whether this is really an improvement, since although we have multiple versions of the same package installed on our system, we can only have one ruby in the environment at any one time. To deal with this nix provides the nix-shell utility which constructs an environment on demand and runs a new shell based on that environment.

nix@salo:~/nixpkgs$ nix-shell -p ruby_2_0            
these paths will be fetched (4.44 MiB download, 22.57 MiB unpacked):
  /nix/store/2l8irkrhvdqmd1h96pcnwv0832p9r901-libffi-3.2.1
  /nix/store/945sd3dbynzpkqdd71cqqpsl8gwi9zsq-ruby-2.0.0-p647
  /nix/store/m74m7c4qbzml7ipfxzlpxddcn9ah8jrs-gdbm-1.12
  /nix/store/zbjyc3ylb9bj3057rk5payv3sr0gnmkc-openssl-1.0.2l
  /nix/store/zsgmhsc8pjx9cisbjdk06qqjm8h89lmp-libyaml-0.1.7
fetching path ‘/nix/store/m74m7c4qbzml7ipfxzlpxddcn9ah8jrs-gdbm-1.12’...
...
[nix-shell:~/nixpkgs]$ which irb
/nix/store/945sd3dbynzpkqdd71cqqpsl8gwi9zsq-ruby-2.0.0-p647/bin/irb

[nix-shell:~/nixpkgs]$ irb
irb(main):001:0> puts RUBY_VERSION
2.0.0
=> nil
irb(main):002:0>

nix@salo:~/nixpkgs$ nix-shell -p ruby_2_4
these paths will be fetched (3.13 MiB download, 15.30 MiB unpacked):
  /nix/store/wly748apb5r37byvvgq85hshgzcahv0y-ruby-2.4.0
fetching path ‘/nix/store/wly748apb5r37byvvgq85hshgzcahv0y-ruby-2.4.0’...
...
[nix-shell:~/nixpkgs]$ which irb
/nix/store/wly748apb5r37byvvgq85hshgzcahv0y-ruby-2.4.0/bin/irb

[nix-shell:~/nixpkgs]$ irb
irb(main):001:0> puts RUBY_VERSION
2.4.0
=> nil
irb(main):002:0>

We haven't even started to scratch the surface in this intro, there's lots of really exciting stuff I've not even mentioned, like how you can always rollback to the environment at an earlier state: every mutation to the environment is recorded, so every time you install or uninstall a nixpkg a new "generation" of the environment is created, and it's always possible to immediately rollback to some earlier generation. NixOS itself takes all these super exciting ideas and applies them to an entire operating system, where each user has their own environment, so ruby for one user might mean ruby2.0 and ruby for another might mean ruby2.4. Hopefully it's clear now how these different versions of the same package can live in harmony under NixOS.

I hope I've managed to convey some of nix's coolness in this short space, if I have then you should definitely check lethalman's "nix-pills"1 series for a really deep explanation of how nix works internally and how to create nixpkgs from scratch. There's also ofcourse the NixOS website2 and #nixos on irc.freenode.net which is probably one of the friendliest communities out there.

Nix pills have been moved to the main NixOS site now https://nixos.org/nixos/nix-pills/

You can even submit pull requests at http://github.com/nixos/nix-pills !

Comment by richardipsum Tue Sep 12 13:38:41 2017

Nice post, and I'm glad you're enjoying NixOS!

I'm a NixOS contributor/committer, and the principle author (and co-maintainer) of the Bundler-based packaging integration. If you or anyone reading this has any feedback and/or questions about NixOS and Ruby (or NixOS in general) feel free to shoot me a message. I idle on #nixos on Freenode (as cstrahan), and you can email me at charles {{at}} cstrahan.com.

Cheers!

-Charles Strahan (cstrahan)

Comment by charles Mon Oct 16 23:05:02 2017