Nix is full of gotchas and it takes quite a bit of time to ramp-up. This write-up shows a lot of the benefits that you can get from using Nix. There are some interesting milestones to reach like setting up a binary cache and then populating it with CI jobs.
One gotcha in docker.nix is that the system part should be fixed to “linux-x86_64”, otherwise macOS users will get docker images with macho binaries in them. At that point macOS users will start seeing build failures, which can be resolved by setting up a nix remote builder machine.
At that point macOS users will start seeing build failures, which can be resolved by setting up a nix remote builder machine
Using dockerTools.buildLayeredImage or nixery should work when building images for foreign systems, as long as no cross-compilation is needed (i.e. the actual binaries are cached).
For buildLayeredImage you could import the package set twice (once with the current system, once with the target) and use the packages from the target - the function just assembles a tarball out of it, and which system that happens on should be irrelevant.
Another way is to have CI build the image and push it to a binary cache. Configure the binary cache on the macOS machine. As long as no rebuild is necessary the local machine can then just pull it from the cache.
The system attribute is what Nix uses to recognize the target system.
There are also some cross-compilation facilities in nixpkgs if you are always building from the mac for example that could be used. Search for nixpkgs.pkgsCross.
Probably one of the best use cases for functional package managers right now is for creating reproducible development environments on any machine. I am looking to set up Nix on both my Mac and Linux servers so my toolchain remains consistent between environments.
I didn’t know about niv, and am a bit curious what it actually does.
E.g., $ niv add adisbladis/vgo2nix is given a github project name, and apparently provides the vgo2nix tool. But how is this built? Using the included default.nix? The niv README shows niv add stedolan/jq as an example, a project which doesn’t appear to bundle any .nix files.
It depends on what you do with the source. The source itself is just a fixed-output derivation, which can represent e.g. a file or directory. In the post @cadey uses import. import parses and returns the Nix expression at the given path. So e.g. import ./foo.nix will parse and return the expression in foo.nix. If the argument is a directory, e.g. bar in import ./bar, import will parse the default.nix file in the given directory. So, summarized,
import sources.vgo2nix {}
from the blog post means: parse and return the path sources.vgo2nix evaluates to. Since this results in a directory, it will parse default.nix in that directory. This (presumably) evaluates to a function / closure, so then this fragment evaluates to:
<some closure> {}
So {} is then the argument to the closure. {} is an empty attribute set (the closest equivalent in other languages is a dict/hash table).
But since a niv source can be any fixed-output derivation, it does not actually have to be a Nix expression (stored as a file). You can do pretty much anything with it that you want. E.g. I also use niv to fetch (machine learning) models and set environment variables to their paths in the Nix store:
Dang, that’s exactly it. I was hunting through the niv documentation because it seemed to be magically doing something more, but it just relies on that default.nix. Solid, thanks!
niv makes it easier to combine stuff from different repositories. Say that you want to pin nixpkgs in a project, you could use fetchFromGithub or fetchTarball to fetch a specific revision. If you ever wanted to update to a newer version of nixpkgs, you would have to bump the revision and update the sha256 hash by hand. niv automates these things. E.g.:
# Add nixpkgs, latest revision from the master branch.
$ niv add NixOS/nixpkgs
# Later: update `nixpkgs` to a later revision.
$ niv update nixpkgs
niv overlaps somewhat with the proposed flakes support in Nix:
vgo2nix is built using its default.nix, yes. I don’t completely understand the point of adding things like jq to projects yet, but I assume that the understanding will come to me at some point. I can be patient.
Its convenient to put that stuff in shell.nix and use it in conjunction with lorri.
For instance, if you are developing in python you might want to use mypy for testing but not require it as a build dependency. It makes sense to throw it in shell.nix. Another use case I’ve found is writing proofs in Coq. This is especially true because opam modules for Coq don’t work properly when globally installed.
Maybe if you are using jq all the time in some project, it makes more sense to throw it in shell.nix than do a global install…
Wonderful to hear that you had a good experience with Nix! I was sold on it at my last job where we used it to administer a fleet of servers for an e-commerce company, and now I run NixOS on all my laptops and use it to reliably install IHaskell.
I’ve tried to like Nix (and NixOS) several times, but it just doesn’t cut it. It’s too awkward and complex, not unlike opinionated functional programming.
There is a steep learning curve and I completely agree that documentation has much room for improvement. Any immediate pain points you hit that we could improve on?
the main nix-env command should be made more ergonomic. I think there already exists “nix” which makes most of this happen. I haven’t checked nix out in 1 year or so, so things may have gone better already in this area.
tutorial walkthroughs for common user and admin activities
tutorial walkthroughs for common devops things. Ansible and terraform are de facto standards, and if nix(os) is trying to replace them, it should replace them well and not haphazardly.
an accessible wiki (arch linux nailed this one, but you need lots of people to pull this off)
As an aside to whoever keeps doing that: it’s not very cool to randomly downvote people you disagree with as “troll”. Typically I wouldn’t mind, but now lobste.rs has started harassing people with a scary warning sign if you get too many (more than 10?) of these in 30 days.
I’m curious (and send Christine an email) what a Docker image would look like if she followed one of the “minimal docker images that start from the ‘scratch’ image” recipes (e.g. this one).
It forgoes the reproducibility achieved via Nix, but might be simpler.
I actually asked about this in IRC, and the response was that she depends on a number of command line tools (like youtube-dl) and so it’s just much easier to use a non-minimal image for her use case.
I’ve done this with some trivial projects, but I’ve found it really hard to use FROM scratch because if you ever need to make http requests out, you need a cert bundle. And if you ever need other tools, you need to make sure you have some sort of libc… and at that point, you might as well just keep alpine around at a bare minimum.
My Alpine image is based on a lot of compromises and the like made over the years. I ended up using it as a “universal base” because I had spent so much time making sure it would work as a generic “do stuff” image. I use it for a lot more than just this site.
Glad to hear it! Thanks for these very insightful nix posts.
@nmattia: lots of deserved praise for niv there ;)
Nice write-up @cadey!
Nix is full of gotchas and it takes quite a bit of time to ramp-up. This write-up shows a lot of the benefits that you can get from using Nix. There are some interesting milestones to reach like setting up a binary cache and then populating it with CI jobs.
One gotcha in
docker.nix
is that the system part should be fixed to “linux-x86_64”, otherwise macOS users will get docker images with macho binaries in them. At that point macOS users will start seeing build failures, which can be resolved by setting up a nix remote builder machine.Using
dockerTools.buildLayeredImage
or nixery should work when building images for foreign systems, as long as no cross-compilation is needed (i.e. the actual binaries are cached).For
buildLayeredImage
you could import the package set twice (once with the current system, once with the target) and use the packages from the target - the function just assembles a tarball out of it, and which system that happens on should be irrelevant.Another way is to have CI build the image and push it to a binary cache. Configure the binary cache on the macOS machine. As long as no rebuild is necessary the local machine can then just pull it from the cache.
Is there a way to avoid setting “linux-x86_64”? That is, have Nix recognize that the target system is linux and build accordingly?
The
system
attribute is what Nix uses to recognize the target system.There are also some cross-compilation facilities in nixpkgs if you are always building from the mac for example that could be used. Search for
nixpkgs.pkgsCross
.Probably one of the best use cases for functional package managers right now is for creating reproducible development environments on any machine. I am looking to set up Nix on both my Mac and Linux servers so my toolchain remains consistent between environments.
I looked into it last night and it has a serious issue on Catalina. One hopes they resolve it soon.
There are relatively straightforward fixes to create a synthetic drive at
/nix
. It’s not official yet, but it works just fine.https://github.com/NixOS/nix/issues/2925#issuecomment-539570232
This is what I use and what I’m going to include in my tutorial post that I’ll be writing in the next week.
I didn’t know about niv, and am a bit curious what it actually does.
E.g.,
$ niv add adisbladis/vgo2nix
is given a github project name, and apparently provides thevgo2nix
tool. But how is this built? Using the includeddefault.nix
? Theniv
README showsniv add stedolan/jq
as an example, a project which doesn’t appear to bundle any.nix
files.It depends on what you do with the source. The source itself is just a fixed-output derivation, which can represent e.g. a file or directory. In the post @cadey uses
import
.import
parses and returns the Nix expression at the given path. So e.g.import ./foo.nix
will parse and return the expression infoo.nix
. If the argument is a directory, e.g.bar
inimport ./bar
,import
will parse thedefault.nix
file in the given directory. So, summarized,from the blog post means: parse and return the path
sources.vgo2nix
evaluates to. Since this results in a directory, it will parsedefault.nix
in that directory. This (presumably) evaluates to a function / closure, so then this fragment evaluates to:So
{}
is then the argument to the closure.{}
is an empty attribute set (the closest equivalent in other languages is a dict/hash table).But since a niv source can be any fixed-output derivation, it does not actually have to be a Nix expression (stored as a file). You can do pretty much anything with it that you want. E.g. I also use niv to fetch (machine learning) models and set environment variables to their paths in the Nix store:
https://github.com/stickeritis/sticker-transformers/blob/master/nix/models.nix
Dang, that’s exactly it. I was hunting through the niv documentation because it seemed to be magically doing something more, but it just relies on that default.nix. Solid, thanks!
I don’t get it either. The github page says “Easy dependency management for Nix projects” but isn’t that what Nix was for?
niv
makes it easier to combine stuff from different repositories. Say that you want to pinnixpkgs
in a project, you could usefetchFromGithub
orfetchTarball
to fetch a specific revision. If you ever wanted to update to a newer version ofnixpkgs
, you would have to bump the revision and update the sha256 hash by hand.niv
automates these things. E.g.:niv
overlaps somewhat with the proposed flakes support in Nix:https://github.com/tweag/rfcs/blob/flakes/rfcs/0049-flakes.md
vgo2nix is built using its default.nix, yes. I don’t completely understand the point of adding things like jq to projects yet, but I assume that the understanding will come to me at some point. I can be patient.
Its convenient to put that stuff in
shell.nix
and use it in conjunction with lorri.For instance, if you are developing in python you might want to use
mypy
for testing but not require it as a build dependency. It makes sense to throw it inshell.nix
. Another use case I’ve found is writing proofs in Coq. This is especially true because opam modules for Coq don’t work properly when globally installed.Maybe if you are using
jq
all the time in some project, it makes more sense to throw it inshell.nix
than do a global install…I use Niv to pin Vim plugins.
https://github.com/bb010g/dotfiles/blob/cb208f6e3b60c240d17b9ea076654b0506e80ba0/nix/sources.json
Wonderful to hear that you had a good experience with Nix! I was sold on it at my last job where we used it to administer a fleet of servers for an e-commerce company, and now I run NixOS on all my laptops and use it to reliably install IHaskell.
I’ve tried to like Nix (and NixOS) several times, but it just doesn’t cut it. It’s too awkward and complex, not unlike opinionated functional programming.
Did you ever try Guix? I am interested in these things but don’t want to invest time in one if I am just going to inevitably switch to the other.
There is a steep learning curve and I completely agree that documentation has much room for improvement. Any immediate pain points you hit that we could improve on?
Here’s a few ideas:
the main nix-env command should be made more ergonomic. I think there already exists “nix” which makes most of this happen. I haven’t checked nix out in 1 year or so, so things may have gone better already in this area.
tutorial walkthroughs for common user and admin activities
tutorial walkthroughs for common devops things. Ansible and terraform are de facto standards, and if nix(os) is trying to replace them, it should replace them well and not haphazardly.
an accessible wiki (arch linux nailed this one, but you need lots of people to pull this off)
As an aside to whoever keeps doing that: it’s not very cool to randomly downvote people you disagree with as “troll”. Typically I wouldn’t mind, but now lobste.rs has started harassing people with a scary warning sign if you get too many (more than 10?) of these in 30 days.
I’m curious (and send Christine an email) what a Docker image would look like if she followed one of the “minimal docker images that start from the ‘scratch’ image” recipes (e.g. this one).
It forgoes the reproducibility achieved via Nix, but might be simpler.
Anyone have any experience/comparisons?
I actually asked about this in IRC, and the response was that she depends on a number of command line tools (like youtube-dl) and so it’s just much easier to use a non-minimal image for her use case.
I’ve done this with some trivial projects, but I’ve found it really hard to use
FROM scratch
because if you ever need to make http requests out, you need a cert bundle. And if you ever need other tools, you need to make sure you have some sort of libc… and at that point, you might as well just keep alpine around at a bare minimum.My Alpine image is based on a lot of compromises and the like made over the years. I ended up using it as a “universal base” because I had spent so much time making sure it would work as a generic “do stuff” image. I use it for a lot more than just this site.
However, Nix basically obsoleted it lol.