Want to use Nix for development but you're not sure how? Concerned about the
fluidity of nixpkgs
channels or not being able to easily install arbitrary
package versions?
When I first heard about Nix it seemed like the perfect tool for a developer. When I tried to actually use it for developing and deploying web apps, though, the pieces just didn't seem to add up.
Trying to figure out whether or not Nix was really suited for development took a lot of research, experimentation, and asking for help. In the end I found answers to all of my questions and finally felt like Nix really was the tool I was looking for. Here's what I discovered:
Nix channels are fluid - isn't this a liability?
Actually, it's an advantage. The stable NixOS channels "only get conservative
bug fixes and package upgrades. For instance, a channel update may cause the
Linux kernel on your system to be upgraded from 3.4.66
to 3.4.67
(a minor
bug fix), but not from 3.4.x
to 3.11.x
(a major change that has the
potential to break things)."
[source]
Basically, the fluidity of Nix channels shouldn't ever result in backwards-incompatible changes, and therefore shouldn't ever cause issues for your project. The advantage is that it will include backwards-compatible bug fixes and upgrades, so you get some good stuff without any of the bad stuff (breaking changes).
But Nix is supposed to be a means for creating reproducible environments - how can this be if the channels are fluid?
Nix is functionally pure - you always get the same output given the same input. So if the Nix channel is updated and you go and install the project to a new machine then yes, you'll get a different output. But in light of the previous point this shouldn't need to be a concern in practice. If you do somehow run into a "works on my machine" bug then the solution would simply be to update the packages on that machine.
If you really, really want to have the exact same package set across multiple
machines you can pin the packages to a specific revision of the nixpkgs
repo.
This isn't recommended, however, because then you won't be able to take
advantage of the channel's binary cache, so you'll have to build everything from
source on each machine (this can take hours). You'll also miss out on the
"conservative bug fixed and package upgrades".
Alternatively you can use a little helper function that always pulls packages from the most recent version of the Nix channel so that all your environments are always on the same page.[example]
That's nice but Nix claims to be able to have multiple versions of the same package side-by-side. This doesn't seem to line up with the fact that most packages only have one version in the Nix channel.
Just because a version of a package isn't in a Nix channel doesn't mean you
can't use it. Nix does allow you to use any number of versions of any package
as long as you're able to give it instructions on how to obtain that package
version. For example, you could pull in a package version from an older NixOS
channel. Or you can use overrides (either at the
user-level[example]
or at the project-level [example]) or fork
nixpkgs
to specify an exact package version. However, be warned that if the
package version you want is too far out of step with what's in your Nix channel
you could end up having to specify instructions for obtaining its entire
dependency graph as well, which gets ugly fast. Package overrides are also
unlikely to be in the Nix binary cache.
There are good reasons why Nix has a single-version policy, though, and for the reasons explained in the previous points this should suffice in most cases. Don't forget, though, that Nix is a functional language, so in a lot of cases you might find that it offers just enough flexibility and power to slap together your own kind of solution. There are quite a few often-overlooked functions in Nix not mentioned in the main documentation - see here for example.
Great, but Nix's overrides system feels a little clunky...
Yeah well, that's what you get for using an actual language for package
management instead of a data file (a la npm, Bundler, Bower, etc.). Nix is a
composable, lazy, purely functional language, not just a data representation.
This gives you a ton more power. Feel like something is too verbose? Write a
helper function. Want to do something really out of the box? Try
runCommand
.
Generally you won't have to use the overrides much, and once you understand how the pieces fit together in the Nix ecosystem it's not too hard to write some helper boilerplate to make the process easier.[example]
Alright, but I want to write something that'll need automatic deployment.
default.nix
doesn't seem to get any say in what channel it uses, though, so how can I have confidence it'll build in every environment when the packages it receives could change at any point?
Again, Nix is a language, not a data representation. Most examples you'll see
will grab the package set through the nixpkgs
value in the NIX_PATH
environment variable:
# default.nix
{ nixpkgs ? import <nixpkgs> {}, ... }:
nixpkgs.stdenv.mkDerivation { ... }
However, default.nix
doesn't need to be a function - it just needs to be any
valid Nix expression which when evaluated by the nix-*
command you're using
(and its arguments) results in a derivation. For example:
# default.nix
let
pkgs = import <nixpkgs> {};
in
{
foo = pkgs.stdenv.mkDerivation { ... };
bar = pkgs.stdenv.mkDerivation { ... };
}
This Nix expression evaluates to a set. However, its attributes are derivations,
so we can use it like so: nix-build -A foo
. Using functions like fetchGit
and fetchTarball
we can easily lock down our default.nix
to use a specific
channel (or any other source of Nix expressions) unless passed a
default.[example]
Found a mistake? See something I missed? Suggestions? Leave me a comment!
This gist is referenced in this comment in Nixpkgs issue #9682: No way to install/use a specific package version?. (The link there is dead, hence this comment.)