Of course, the standard answer is to spin up a ton of Docker containers. Docker works, but it looks to me like a local optimum rather than a truly painless solution. It sucks as a build system, and Dockerfiles not being reproducible is the default outcome that needs significant extra care to avoid (how many times have you seen apt update or some equivalent in one)? Besides, why should I have to worry about a whole another OS inside my main OS, with potentially different tooling and conventions, when what I really want is just specific versions of a couple of tools?
I think we've gotten used to development environments being a shitty experience to the point where it seems part and parcel of programming, but when you take step back, it's apparent that the situation causes a lot of frustration and wastes a lot of time. To me, Nix's combination of package manager and reproducible build system looks like one of the most credible ways out. NixOS' declarative configuration and rollbacks are nice side benefits too, for server admins and newbies respectively. Nix just needs a lot more polish. I'm not about to introduce a tool where the most common workflow is still considered experimental. For now, I'll keep using Docker, but I watch Nix with interest and can't wait until its UX matures.
EDIT: Removed claim that Bazel and Buck's creation was motivated by multi-language support. Looks like the main motivations were speed and reproducibility.
I have staunchly refused to allow this at my current employ, and I’ve been there long enough where I can steer this.
This isn’t acceptable and all it does is help introduce inconsistency and regressions.
I realize I am fortunate in that I can effect change at my job in this arena.
Eventually I forced everyone to work with a controlled dev environment on a remote server. They can work over ssh with command line tools or VSCode. Dev environments get rigorously maintained to mirror production. I got some grumbling about latency (everyone works remotely from around the world), but that turns out to present much less friction than the almost-daily debugging sessions and dealing with MacOS vs Windows vs Linux inconsistencies.
The longer answer is, earlier in my career I ended up spending a not-insignificant amount of time helping people debug things, and about 70% of the time the issue was their local build environment. Everyone did it differently and it was very messy.
I only made a few "rules" to follow. 1.) Your local folder structure must mirror exactly the structure in our central repo. 2.) Must use relative paths, which works when you follow rule 1. 3.) No ad-hoc "new shiny things" without team buy-in. If it can be used locally only and won't affect the team obviously I don't care.
Do you use a monorepo at work, or do you have like a repo for a 'local dev environment' or something like that?
But the whole thing seems like it's designed to express more powerful things than you can easily map to an app store style install/uninstall button, rather than solving the problem with the least powerful tools that give the user the highest level interface possible, and leave as much as possible for the computer to figure out.
The core philosophy of Nix is so damn solid though, and that's the real innovation here. As long as its philosophy manage to stick around, then it's ok.
I've given it a try and it's quite incredible how easy certain things are.
The problem is the Nix language and the developer experience are really rough.
There's a chance that Nix could figure this out or a competitor with Nix-like ideas could become mainstream. That's my hope anyway.
It would be a shame if the ideas behind Nix got dismissed because of the current issues with Nix.
Just learn, use, promote best practices and stop forking the ecosystem _even_ further...
There, I got that off my chest.
[0]: Though apparently darwin (mac) doesn't support sandboxing by default, so you can bypass that but anyway
Unless you restrict your nix files to specific channel revisions, which when I had to deal with it was poorly documented, and involved searching through specific channel commit hashes in a particularly opaque way, you also can't know that your nix derivations will ever work again.
A number of people on my field used nix as a way to make their research code repositories reproducible, and everything broke within around three years.
The value of most reproducibility at the Dockerfile is that we're actually agnostic to getting a byte-exact reproduction: what we want is the ability to record what was important and effect upgrades.
By that logic every binary artifact is a "reproducible build". The point of reproducibility isn't just to be able to reproduce the exact same artifact, it's to be able to make changes that have predictable effects.
> The value of most reproducibility at the Dockerfile is that we're actually agnostic to getting a byte-exact reproduction: what we want is the ability to record what was important and effect upgrades.
More or less true. But we don't have that, because of what grandparent said; if a Dockerfile used to work and now doesn't, and there's an apt-get update in it, who knows what version it was getting back when it was working, or how to fix the problem?
So in practice.. if the build described in the dockerfile breaks, you notice when you’re changing / extending the dockerfile.. which is the time and place where you’d expect to need to know. My guess is that most people complaining about deterministic builds for containers are not using registries for storing images, and are not deploying to platforms like k8s. If your process is, say, shipping dockerfiles to EC2 and building them in situ with “compose up” or something, then of course it won’t be very deterministic and you’re at the mercy of many more network failures, etc
Right, but if you're doing that then you probably don't need to bother with the docker part at all.
> you notice when you’re changing / extending the dockerfile.. which is the time and place where you’d expect to need to know
You notice, sure, but you can't see what it was. Like, sure, it's better than it failing when you come to deploy it, but you're still in the position of having to do software archaeology to figure out how it ever worked in the first place.
Like, for me the main use case for reproducible builds is "I need to make a small change to this component that was made by someone who left the company 3 years ago and has been quietly running since then", and you want to be able to just run the build and be confident it's going to work. You don't necessarily need to build something byte-for-byte identical to the thing that's currently running, but you do need to build something equivalent. The reproducibility isn't important per se but the declarativeness is, and with Docker you don't get that.
My dream system would be CI which gives me a gigantic object graph and can sign the source code from the ground up for every single thing including the compiler, so when a change happens you can drill down to what changed, and what the diffs were.
Nix provides the tooling to do reproducible builds. Meanwhile docker is a wrapper around the tools you choose.
Also just to note, docker does allow you disable network access during builds. Beyond Dockerfile, which is a high level DSL, the underlying tech can do this per build step (in buildkit LLB).
``` FROM python:latest ADD . RUN pip install foo ```
If I run this today, and I run this a year from now, I'm going to different versions of `python` and `foo` and there is no way (with just the Dockerfile) to know which version of `foo` and `python` were intended.
Nix on the other hand, forces me to use a git sha[^0] of my dependency; there is no concept of a mutable input. So to your point it's hard to 'upgrade' from version a -> b in a controlled fashion if you don't know what `a` even was.
[0]: or the sha256 of the depedency which yes, I understand that's not easy for humans to use.
The degree to which this guarantee is useful or necessary depends on your use case.
The reason why Nix users "refuse" to containerize is that Nix packages and their associated ecosystem come with a host of benefits that their containerized counterparts do not.
Here is a flake that builds a Go app and a Docker image for it (based on headless Chrome): https://github.com/aksiksi/ncdmv/blob/aa108a1c1e2c14a13dfbc0...
And here is how the image is built in CI: https://github.com/aksiksi/ncdmv/blob/aa108a1c1e2c14a13dfbc0...
2. Everything is pinned to nixpkgs, including dependencies. Anyone who builds this image will get the exact same versions (vs. apt-get update in a Dockerfile pulling a more recent version). It’s just sqlite in this case, but you can imagine how it would work with more dependencies.
3. It is trivial to build a “FROM scratch” image - in fact, that’s the default behavior in Nix, because Nix implicitly includes all runtime dependencies alongside your binary. This is less of a challenge with Go, but YMMV with other languages.
4. You can define your entrypoint script - or any other one-off script - in-line. Not a huge advantage, but still quite useful.
There is even an alternative pattern that allows you to reap these same benefits directly in your Dockerfile:
https://mitchellh.com/writing/nix-with-dockerfiles
Hope that helps.
You can use Nix to build containers. Containers on their own don't guarantee reproducibility, especially if the build process isn't static and pure ( how many times do we `sudo apt update` inside a Dockerfile )?
And not everything is going to be containerizable. That only works for most applications. What if we're trying to manage our cloud servers? That's where Nix really shines.
Do you really think that Nix developers don't know how to containerize applications? You think people are using Nix because they refuse to learn how to containerize, and therefore opt to learn a _much more_ difficult and arcane build process? The logic doesn't track there.
The entire reason why I use Nix in the first place is because it allows me to containerize with _better_ reproducibility than docker itself.
I do get where you're coming from though. It's not immediately clear that Nix can do all this stuff. Nix is a lot more than just "glorified weird package manager".
At its core, Nix is a way to specify dependencies in a mathematically sound manner. Once you have that pure dependency graph managed with Nix, you can start doing the _real_ fun stuff.
Like, you can containerize it. Or you can create a VM from it, or an ISO, or a NixOS distribution with _only_ that package installed.
Nix actually makes containerization _easier_, not harder. But yes, I empathize. Nix is a mess and it is difficult to understand, it will take a few more years before it is fully settled.
In the meantime? I'm going all-in on Nix (the philosophy, not necessarily any particular variant) because I really strongly believe this is the way forward.
You have identified part of the problem.
Nix is attempting to be better than containerization.
Saying "improvements aren't necessary because we already have 'good-enough' technology" is a meaningful argument when the improvements aren't significant.
In my view, they are significant because Nix can be used to create a fully featured OS instead of just a VM.
Look up Bootable Containers project by RedHat [0]. Fully featured OS built from a Containerfile, bootable on bare metal.
I agree that Nix design is much better than Docker, and has a bunch of features that OCI ecosystem doesn't (e.g. remote builds[1], partial downloading of the build tree, non-linear build process[2], nix store import/export, overlays, I/O isolation, much better composability), but "creating OS instead of VM" [did you mean container?] is not one of them.
[0] https://github.com/containers/bootc
[1] You can use DOCKER_HOST, and I'm happy that this option is there, but Nix does it better.
[2] Perhaps with BuildKit it's no longer true, I haven't checked what happens if you have multi-staged build with one stage depending on multiple previous ones (which are otherwise unconnected). I think Earthly can parallelize this scenario https://earthly.dev/
lxc: 2008
docker: ~2012
With Nix I still think it's a net positive, but the "different kind of work" side of the equation is pretty large. That's why we're building Flox [1]. The imperative user interface of a package manager (flox install, flox search, etc) that builds out a declaratively-configured, reproducible, cross-platform developer environment. I really think it nails the user experience by keeping that "different work" side of the equation small, and (I hope) just gets out of your way.
[1]: https://flox.dev
1) The Homebrew package is a cask that installs also Nix. While I like Flox, I don't want my systems to be married to it. Yes, I know about install option with "generic Nix", but I'm using Homebrew with Brewfile both in macOS and Linux, and I would like the Homebrew package to be just Flox.
2) Documentation is OK for getting started, but not for anything more than that. There are nice manifest.toml examples for many use cases in floxenvs[1] but you need to find those first. Also I'm not sure how I feel about inline shell scripts in toml. While it works, separate files would be easier to handle, at least for me.
That said, the repo is public[1] and if there’s specific things you’d like to see or would find helpful you could open an issue. That would be really helpful, but there’s obviously no obligation to do so.
I would argue that Nix does not make trivial environment setup easy. One of the reasons I love Flox for my own development is that I can just "flox install" packages. That doesn’t really exist for project-specific packages with Nix ("nix profile install" exists, but that installs globally). I would say give Flox a try and see if you actually run into issues. If you do, we're pretty good about fixing them as they come up.
It has also not been my experience at all that Nix packages are "often unmaintained and simply broken." I use Nix every day for work, and I use it to configure all of my personal machines (on both macOS NixOS). Can't say that's ever really been a problem for me.
Everything just worked outside of the Nix environment so ended up dropping it.
That describes how to get set up with Rust. Long story short, cargo depends on a linker and brings it along, but doesn’t expose it to other programs via PATH. If there are build scripts that call out to rustc or a linker directly then you need to explicitly install one. This is intentional, but yeah it can be surprising when you get an error about not finding a linker.
You also need libiconv on macOS for reasons, but all of this is described in the link above.
The model of having packages on Github with pull requests scales very well.
Therefore, you could argue that people are getting things done with Nix.
This is a meaningless point. Different distros split packages differently.
At least, that's my personal experience.
However it is clear that the Nix folks are quite productive with maintaining packages. So that statement is judgemental, as it implies that all those packages are not ‘the right things to get done’.
With Nix I usually ended up in a situation where I had to both figure out how to configure my environment _and_ fix the upstream Nix packages. At that point it’s just easier to stick to Homebrew and some custom shell scripts.
Without people getting shit done with the tools we’ve built, there would be no demand for better tools and no need to write them.
Without better tools, the things we can get done are limited. Better tooling is an exponent to our productivity. The things we can accomplish today would have been nearly unimaginable nearly thirty years ago.
Nix & NixOS have made me much more productive. Maintaining a desktop is effortless. If something breaks, I can simply reboot to a previous installation. I can also try software without installing, and develop parallel projects relying on dependencies that would be mutually incompatible in a regular imperative package manager.
But I recognize that if you need to do something slightly unusual, documentation is incomplete and scattered. For simple things, my contrarian view is that Nix is not hard at all. The subset of Nix I use can be learned in a couple of hours. I think the trick is to avoid getting sucked into packaging complex software with messy build systems. If the stack you use is well packaged in NixPkgs, it's a joy to use. If it's not, it's better to stay away.
Like what? Witing an entire operating like Linux or Windows NT from scratch?
I do keep an eye on Nix-related stories to get a sense of whether or not I should change that stance. So far, nothing has led me to change it.
How do you feel about Kubernetes?
It would be good to have some interesting tasks to do?
I think the tools should do also much of the work. I actually prefer batch systems that are a simple execution of a program against a dump which are just process all the data and generate data with the new states than a networked online system that breaks all the time and due to DNS
Micro services keep me awake but a simple CSV processing I can fix in my own time.