Skip to content

Instantly share code, notes, and snippets.

@novaluke
Last active April 29, 2023 17:23
Show Gist options
  • Star 49 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save novaluke/e2f67c65c2e135f16a8e to your computer and use it in GitHub Desktop.
Save novaluke/e2f67c65c2e135f16a8e to your computer and use it in GitHub Desktop.
I want to use Nix for development, but... -- answers to common concerns about Nix

Nix seems perfect for developers - until I try to use it...

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!

let
channel = "nixos-15.09";
# We need at least *some* set of packages in order to use `wget` and
# `runCommand`, so we temporarily borrow the default packages
sysPkgs = import <nixpkgs> {};
# Use `runCommand` to grab the SHA of the latest build of the channel. This
# ensures that the `nixpkgs` set we end up with has already passed through
# Hydra and therefore has passed its tests and has a binary cache available.
latestRevision = import (sysPkgs.runCommand "latestRevision"
{ buildInputs = [ sysPkgs.wget ];
# Force the input to be different each time or else Nix won't check for
# updates to the channel next time we evaluate this expression
dummy = builtins.currentTime;
}
''
# nixos.org/channels/$channel always points to the latest released
# revision of the channel, which contains a file with its git SHA. Once we
# have it, we have to wrap it in quotes so it will become a string when we
# `import` $out
wget -O - https://nixos.org/channels/${channel}/git-revision |\
sed 's#\(.*\)#"\1"#' > $out
'');
# Now that we have the SHA we can just use Github to get the tarball and thus
# the `nixpkgs` expressions
pkgs = import (fetchTarball
"https://github.com/NixOS/nixpkgs-channels/archive/${latestRevision}.tar.gz"
) {};
in
{ nixpkgs ? pkgs }:
nixpkgs.stdenv.mkDerivation { name = "foo"; }
let
overlays = self: super: {
# Make "xbmc" use the "python26" package, instead of NixPkgs default
# python version.
xbmc = super.xbmc.override { # Refer to the package being modified via `super`
python = self.python26; # Refer to packages it uses via `self`
};
};
in
# The key here is to pass `overlays` in to `nixpkgs`, so that the
# packages defined in `overlays` override the ones in `nixpkgs`
{ pkgs ? import <nixpkgs> { overlays = overlays; } }:
pkgs.stdenv.mkDerivation { name = "foo"; }
@toraritte
Copy link

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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment