I remember Target funded some earlier Nix work like lorri.
Interestingly, I found operating at this level to have been a necessity if I wanted to fix or write a derivation. Otherwise I would face random errors and be left clueless as to what the problem was.
Writing a derivation builder (nix pill) was illuminating.
That was a really special team. Nobody "let" me use Nix, we just did it because it made sense to us, and I went out of my way to support it internally not because I felt pressured to do it, but because I was enthusiastic about making it work. The culture there reminded me of this story[1] about getting Python into production at Goldman—and it turns out that the VP who built the supply chain team at Target started his career in strats at Goldman and was heavily inspired by the same Armen from that story :)
[1]: https://news.ycombinator.com/item?id=29104401
After leaving Target, I also used Nix for my own work setup at CX Score. That was a seed-stage startup and I didn't want to push Nix on anybody else, but it made my own work setup much nicer.
Most recently I moved to Mercury where we use Nix at a pretty big scale (several hundred developers). We have a team that handles most of it so I haven't had to do much myself, but I've helped a few people work through Nix issues just because I could :) I'm also one of the weirdos who uses NixOS on my work laptop instead of getting a macbook.
And, in parallel, I've been using NixOS on all my personal machines ever since I first discovered it. That was relatively shortly before joining Target, so, if I had to guess, late 2015 or early 2016. Coming up on 10 years shortly, even though it really does not feel like it...
I love NixOS and was excited to see Mercury using it. What made Mercury choose it and what have the advantages been for a company like that?
The key insight is that it's been able to scale and still work pretty well even as Mercury is hitting a few hundred developers. There's a bit of friction here and there, but the overall dev experience is pretty good; in a number of ways, it's distinctly better than what I've seen in places that heavily use Docker.
It's cool to see some of the most cutting edge infra teams coming around to a tool that came well before Docker.
Since you're presumably using nixos VMs a lot, I'd be curious if you have any thoughts on this project. It's Nix hypervisor + microvms as an alternative to containers
I have used Nix on and off for work since 2018. We are using Nix at my current employer for two projects. I agree that if you want to go beyond some simple dev shells, for any realistically complex project you really need Nix Pills-level understanding (+ a good idea of how things work in nixpkgs).
It's a two-edged sword - the learning curve is very steep, but once you have that level of understanding, it's a superpower. We heavily use CUDA/ROCm machine learning stacks and Nix makes it much easier to ensure everyone is using the same development environment. Switching between different branches that may have different sets of dependencies/dependency versions always works. Also, it's much easier to stack custom patches on dependencies and caching avoids all the rebuilds.
I have a theory that a problem for Nix understanding and adoption out of all apparent proportion is its use of ; in a way that is just subtly, right in the uncanny valley, different from what ; means in any other language.
In the default autogenerated file everyone is given to start with, it immediately hits you with:
environment.systemPackages = with pkgs; [ foo ];
How is that supposed to read as a single expression in a pure functional language?Well, in Haskell the following is technically single expression:
do with pkgs; [ foo ];
(Eg using 'let {with = pure; pkgs = 1; foo = 2}' makes the above type check and compile.)But extreme nerdery and nit-picking aside, I agree that the choice of syntax in nix unfortunate here.
Unfortunately it is also used with the somewhat confusing `with` operator which I personally avoid using. For those of you who aren't familiar, it works similar to the now deprecated javascript `with` statement where `with foo; bar` will resolve to `bar` if it is in scope, otherwise it will resolve to `foo.bar`.
Technically, in JavaScript it's `with (foo) bar`.
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- It uses `;` in the same way as `assert`, whereas `let` uses a whole other keyword `in`.
- It uses attrsets as reified/first-class environments, unlike `let`, which lets us do `with foo; ...`.
- Since it uses attrsets, we can use their existing functionality, like `rec` and `inherit`; rather than duplicating it.
I've been using Nix for over a decade (it's even on my phone), and I've never once written a `let`.
(I agree that the shadowing behaviour is annoying, and we're stuck with it for back-compat; but that's only an issue for function arguments and let, and I don't use the latter)
> Since it uses attrsets, we can use their existing functionality, like `rec` and `inherit`; rather than duplicating it.
`let` supports `inherit`, and is always `rec`. Or is that your point, that it is needlessly duplicated functionality?
Functions with default arguments are also very useful; especially since `nix-build` will call them automatically. Those are "always `rec`" too, which (a) makes them convenient for intermediate values, and (b) provides a fine-grained way to override some functionality. I used this to great effect at a previous employer, for wrangling a bunch of inter-dependent Maven projects; but here's a made-up example:
{
# Main project directory. Override to build a different version.
src ? pkgs.lib.cleanSource ./.
# Take these files from src by default, but allow them to be overridden
, config ? "${src}/config.json"
, script ? "${src}/script.sh"
# A couple of dependencies
, jq ? pkgs.jq
, pythonEnv ? python3.withPackages choosePyPackages
, extraDeps ? [] # Not necessary, but might be useful for callers
# Python is tricky, since it bakes all of its libraries into one derivation.
# Exposing intermediate parts lets us override just the interpreter, or just
# the set of packages, or both.
, python3 ? pkgs.python3
, choosePyPackages ? (p: pythonDeps p ++ extraPythonDeps p)
, pythonDeps ? (p: [ p.numpy ])
, extraPythonDeps ? (p: []) # Again, not necessary but maybe useful
# Most of our dependencies will ultimately come from Nixpkgs, so we should pin
# a known-good revision. However, we should also allow that to be overridden;
# e.g. if we want to pass the same revision into a bunch of projects, for
# consistency.
, pkgs ? import ./pinned-nixpkgs.nix
}:
# Some arbitrary result
pkgs.writeShellApplication {
name = "foo";
runtimeInputs = [ jq pythonEnv ] ++ extraDeps;
runtimeEnv = { inherit config; };
text = builtins.readFile script;
}Personally, it's the fact that there are 57698 ways of doing something and when you're new to Nix you're swarmed with all the options and no clear way of choosing where to go. For example, the docs still use a shell.nix for a dev shell but most have moved to a flake-based method...
I always recommend starting with devenv.sh from the excellent Domen Kozar and then slowly migrating to bare Nix flakes once you're more accustomed.
A language server with autocomplete and jump-to-definition would go a long way to making nix more accessible. As it stands, I generally have to clone nixpkgs locally and grep through it for definitions or examples of things related to what I'm doing, in order to figure out how they're used and to try to understand the idioms, even with 4 years of running NixOS under my belt and 3 years of using it for dev environments and packaging at work.