Writing nix is like writing functions but in order to remember the arguments and fields of those arguments in another file that you can only access through your browser. Look at any nix file, and tell me where each variable is coming from.
I've moved to Atomic desktops and can't name anything I am missing. For dev stuff there are containers.
Flakes is an "experimental feature" of Nix itself, but it has shipped in stable releases of Nix for years now. There was a time when you had to build an unreleased version of Nix to play with flakes but that was a long time ago.
Whether you use a rolling release for the package set, e.g., nixos-unstable or nixpkgs-unstable, is orthogonal to whether or not you use flakes.
Fwiw, I generally prefer to run the unstable branches of Nixpkgs. Occasionally you hit something you have an opportunity to fix yourself, but that's not very frequent these days, even on the unstable branches.
If it was a function call you could just go-to-definition.
Environment variables have a similar issue. It's often hard to know what they do because they could be used by basically anything at any time.
I am trying to create a tool to help see exactly where and by which program any environment variable was set/exported since boot.
This is still in the conceptual phase but I'm looking into linux' ftrace to achieve this. Any ideas or pointers are welcome.
In any case, from the article, what does not apply to Guix, too? I am leaning towards Guix because of its language (Scheme, i.e. Lisp-y), but I wonder about the differences between the two, today (besides userbase and hype).
When I asked a long-time Nix vet why he thinks people leave, he provided the most insightful answer I've seen yet: they just don't try hard enough.
Nix is hard if you need to build something difficult to package or something that has very unusual or dirty build processes. If you need to that regularly, Nix is not worth the effort unless you are an organization that values reproducibility and is prepared to pay for its cost upfront. If these particularly messy usecases are something that you don't encounter so frequently, you can always use an escape hatch to be able to make your install impure. For instance, distrobox is quite convenient to run e.g. an Arch or Ubuntu container. In my case, this has been helpful to run Julia, whose Nix packages are quite brittle.
Most of the benefit of nix for me is maintaining a configuration for a couple computers in a way that's easy to backup, upgrade, and recover with.
Mine has been going on since 2016, what am I doing wrong?
Here’s a very small example:
https://github.com/sshine/nix/blob/main/shared/home-manager....
My servers don’t have that many dotfiles because most server software can be configured in /etc (zsh, vim), while my work computers have a lot of dotfiles symlinked into ~/.config/ via Nix, e.g. VSCodium, ghostty, …
Most people prefer to Nix up their dotfiles, which provides some advantages (e.g. universal styling via Stylix), but the main drawback that I’m not buying is: I can’t share my app-specific config with non-Nix users.
But if you’re looking for a cheaper (in terms of complexity) dotfiles manager: https://github.com/yarlson/lnk
For example with Debian I might do something like try to get my GPU to work by installing nouveau, try various configs, then uninstall nouveau and install the Nvidia proprietary drivers. But then when I finally got the GPU working, I'd worry. Are some of those config changes I tried for nouveau still affecting my system? Has all that mucking around left my GPU forever in a suboptimal state?
With Nix, it might take just as much tinkering to get the GPU to work. But once I'm done, I know that my system is exactly as I've defined it in configuration.nix. Anything else I tried along the way will just sit inertly in /nix/store until the garbage collector wipes it away.
Docker with a Debian Stable base will solve the problems listed for most users, with 1% of the time investment. The author used Nix for 8 years, and I'm not surprised it looks a bit simpler to them now.
True. In my case I just enabled nix-ld[0,1] and now everything works flawlessly.
I wish Nix-ld was just bundled into Nix
I tried to learn Nix (the language) a couple years ago, didn't like it and got nowhere. Now, two months ago I heard I would be getting a Windows machine at my new client, so I really wanted a way to deterministically generate a Linux VM that worked & felt exactly the same as my usual setup. So I thought back to Nix(OS) and this time I skipped trying to learn Nix and just tried to absorb it from examples and code snippets I would find online (very reminiscent of my experience with Emacs many years ago). Often I would also ask Gemini to explain things to me. Only a few afternoons later I had a working VM[0] that looked and felt exactly as the desktop environment I have carefully curated over 15 years. Now, several weeks later, I also have built a live CD image with that same config and will soon roll out my Nix config to all my Ubuntu machines & Debian servers. I won't look back.
I won't deny, though: I still don't like the language too much and I think it is tied too much to building an OS, as opposed to anything else. Documentation is sparse and mediocre at best. But overall it's alright! Things are usually not that complicated, and most of the time they just work! (And if they don't, it's surprisingly easy to navigate the nixpkgs source code and look up what's happening under the hood.) In any case, my new Nix config is so much better than the set of bespoke Bash scripts I've written over the years to (reproducibly?) configure my Ubuntu desktop with i3wm for me.
[0]: nixos-generators is magical when you first try it! https://github.com/nix-community/nixos-generators (Fine print: Of course with the benefit of hindsight I now know that it's merely a dumb wrapper around things that Nix provides out of the box but oh well.)
The main reason I decided against home-manager was that it makes my simple symlink setup (~/.foo -> ~/.dotfiles/foo) virtually impossible: Symlinks will always point to the read-only Nix store in one way or another. See https://github.com/nix-community/home-manager/issues/3514 or one of the countless other tickets on the topic. Through this episode I also learned that home-manager isn't exactly well-documented, either, and also quite opinionated. (Which, to be fair, is not a bad thing – if it works for you, it probably works great.)
Finally, not using home-manager was also a risk management decision in that it's probably best to not go all-in on Nix, NixOS, home-manager etc. all at once. Start with the simplest possible config that gets you running, then iterate.
I’m forever intrigued by the promises of nix, but I have finally accepted that I am not the target audience for what they have built. The nix language is fundamentally at odds with general purpose usability. Even using the package manger on other distros is fraught with gotchas.
On an off topic note, I find it fascinating how both my father and grandfather, who are/were quite technically competent, seem to become increasingly and possibly willfully helpless about computers as they get older. I find myself wondering if at some point I will also experience exhaustion of whatever internal energy is needed to fuel technical fluency.
I have contributed or tried to contribute to many distributions (heck, I even worked for a commercial Linux distribution when I was young) and contributing to nixpkgs was an order of magnitude easier than other distributions. Part of it was the GitHub/PR-centered workflow, but the other part is that Nix is a small functional language and the package definitions are not in some arcane weird format like RPM spec files or Debian rules. Also, Nix makes it much easier to refine a derivation/package without actually having to install it on your system.
Writing a new package from scratch based on Nixpkgs is far easier than for other distros, and easy packaging tasks are much easier with Nixpkgs than with other distributions.
(It's also much easier to distribute packages without standing up new infrastructure with Nix, since it's source-based and will fall back to source-based builds if you have no established mechanism for distributing binaries.)
I stick mostly to reasonably popular Debian packages. My thinking is that if one of them has a serious vulnerability it will get fixed quickly or a lot of people have much bigger problems than me. I always worry about niche package systems, where it should be much easier to sneak in something malicious and it could linger there for a long time undetected.
- is this actually the binary that comes from that code?
- is that code trustworthy?
- is this binary trustworthy?
Nix focuses on the first. If you can solve that problem, there's still nothing preventing bad guys from publishing malicious code, or from distributing malicious binaries which did not actually come from the code that they claim to, but it forces them to act in the open where they're more likely to get caught--anybody can verify that the correspondence between code and binary doesn't hold.
So if you're worried that one dark day, `curl whoever.com/whatever.sh | bash` will suddenly contain malware when it didn't before, using nix instead will protect you... until you manually approve the malicious update. It's up to you to come up with reasons why you should or shouldn't do that, but at least there will be a commit in your code where things went bad.
If you're wondering whether whatever.sh is safe in the first place, and you want some council of elders to pinky promise that it's safe to run, then I don't think the nix ecosystem has much to offer you.
Personally, I think that solving the latter problems without a robust solution to the first problem is sort of a band-aid solution. So I'm a big fan of the nix approach to supply chain security, but it's important to be aware of what you're not getting, which is a promise that any bits are trustworthy in the first place.
I agree even with the following paragraph in principle:
"If you're wondering whether whatever.sh is safe in the first place, and you want some council of elders to pinky promise that it's safe to run, then I don't think the nix ecosystem has much to offer you."
For me it is not so much about what the council of elders says, but more about when FAANG and Co. are OK to run a binary I think needn't worry about the rest. Or to think this further, they should care more about Nix than I should.
For example, you can see where the xz sources get pulled from in the src section here:
https://github.com/NixOS/nixpkgs/blob/nixos-25.05/pkgs/tools...
As usual, wherever you get your software, if someone at the source sneaks in something malicious and no one notices it it gets in there. NixOs has no special mitigations against that (AFAIK).
But you can be reasonably sure that the binary you have matches the official source of the software, with maybe some reviewed patches to get it to work in Nix's environment.
The binaries are cached, so you don't have to build everything yourself. There is a command to rebuild the software from source yourself. Most packages are reproducible, about 95% of the distributed gnome version: https://reproducible.nixos.org/nixos-iso-gnome-r13y/
In order for people to review Nix package definitions and patches, do they need to have their keys signed by other Nix contributors they meet in person like Debian contributors do?
One fun example is the test database: five years of accumulated migrations were relatively slow to run in various portions of the pipeline. I made a nix derivation that runs postgres, runs migrations, seeds some test data, runs some tests, and then saves the data directory into $out. CI jobs can then load the DB by building that derivation and copying the data files into a local directory. If no migrations or other dependencies have changed, this is virtually instantaneous.
That means `nix-env -iA <packagename>`.
Is this approach using the full power of nix? No. But in a “worse-is-better” kind of way, just doing this has given me a more stable system than I had with Homebrew. And that’s all I wanted from nix. Flakes and nix-shell are just additional things that I’ve gotten some benefit from over time.
I use emacs. The emacs LSP mode starts up a language server process for a language like Python or Go. If I use Nix to manage development dependencies like my compiler, linting tools, and even language-specific dependencies, then how do I get the LSP and emacs to use the correct Nix-y set of dependencies? To make things extra complex, note that I often work on multiple interdependent projects concurrently; I imagine that makes things even gnarlier.
Is there a sane way to manage this sort of thing? Do I end up managing a per-project installation of emacs??
Or at least that's how I do it. That will make `ruff` or `pyright-langserver` point to whichever version of the tool is bound to that project. So when I run `nix flake update` it updates the tools for only that project and I don't get any crosstalk between projects.
As for whether a given linter or language server should be configured for that project, helix looks for .helix/languages.toml in the CWD to decide which ones to load, so that bit of config I handle independently from nix, I just add that file to the repo. Presumably emacs has something similar.
So to answer your question: you only get a project-specific version of emacs if you add emacs to the project's devshell. Otherwise whatever was already on your PATH will be used.
Yes.
But if you use an editor for many projects at once, it'll work better if different projects can have different PATHs set, in which case you can use direnv + an appropriate direnv plugin for your editor. That way you don't have to worry about launching your editor from inside nix-shell or whatever.
IIRC IDEA is still defective in this way, not allowing per-project environment variables, but Emacs, Vim/Neovim, and VSCode all handle this nicely.
There are similar plugins for vscode, vim, etc
However is some cases when it could be little more tricky, for instance I'm using LSP for Scala (https://scalameta.org/metals/) which on start trying to automatically detect JDK version for project from environment. So if my project depends on JDK 11 it might required for me manually start LSP from nix-shell to force it use right JVM version.
With Rust is in someway similar, I need to bring some shell with 'rust up' setup so it could install everything for rust-analyzer then emacs can use it.
Regardless emacs itself is few options to manage emacs packages in nix way but for me it not really gives anything so I just use it as on any other OS.
- Install nixpkgs emacs or emacs distribution to your user profile so it links into every shell you open.
- Configure system-wide settings with the default emacs config, or with home-manager (https://mynixos.com/home-manager/options/programs.emacs)
- Each project (or group of projects) gets a Nix flake with a derivation specifying which dependencies or LSP packages are needed.
When a devshell is spun up, you should have the nixpkgs version of Emacs with your system-wide config and project-specific modifications, handled via version-control. It is a hassle, but in my experience the ROI is very worthwhile. YMMV.
My Emacs then sets up an LSP per project, and just picks up the correct LSP implementation by PATH.
No per-project Emacs madness. (Though I guess you could do that if you wanted by including copies of Emacs with all the appropriate packages in your development environments.)
Particularly since I do a lot of ML work - I never figured out how to handle mixed Python/C++ code with dependencies on CUDA.
It's just way easier, even if not reproducible, to build an environment imperatively in Docker.
I don't have the time or desire to switch all my python/ML work to more conventional Nix, and haven't really had any issues so far.
Reproducibility is the holy grail, IMO. It is so valuable that any system that actually achieves it will find some longevity and eventually be hammered into a useable form. I believed in the promise when AWS was all about amis. Then I believed in the promise with docker. It seems something like Nix is a natural next step in this evolution.
I want it to succeed enough that it gets easy enough for me to use. But for now I'll stick with macOS for my laptop and docker with alpine for my deployments.
> I'll stick with […] docker with alpine for my deployments
Huh. In my experience Alpine is the worst possible base image to use if you care about reproducibility.
- The package index's URL cannot be pinned (URL expires on a regular basis)
- The downloaded package index itself (tarball) cannot be pinned/cached, either, because old package versions (i.e. the URLs in the tarball) become unavailable after a few weeks.
Meanwhile:As with anything in engineering there are tradeoffs, there is no singular perfect solution. My choice of alpine was from years ago when I examined the amount of included code in competing docker images and I found the alpine solution was geared towards the least amount of code required to achieve the desired goal (e.g. running a server of a particular kind). That line of thought almost certainly requires a new examination of the available options since the world has changed since I last did a deep dive into docker base images.
In a perfect world I want a base image that doesn't contain a single bit extraneous to executing the services I deploy (for whatever expansive definition of "necessary" I arbitrarily choose). And in that perfect world the image is completely reproduceable from a static definition. Oh yeah, and it should be stable/robust, free from exploits, etc.
So it might be too strong to say "holy grail" if one interprets that as a singular goal that needs to be fulfilled. I meant it as "one among many" in the list of virtues I look for.
That being said, there are a lot of minimal *nixes these days and I would expect Nix would be a contender in that realm as well.
https://wiki.systemcrafters.net/guix/nonguix-installation-gu...
du -sh /nix
I specifically remember the glibc 2.26/ucontext desaster that triggered an rebuild of the nix world and exhausted all disk space that i had. du -sh /nix
187G /nix
This is with nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
Cleaning up some direnv versions and a `nix-collect-garbage --delete-older-than 5d` would bring it down to around 40G. For me this isn't _too much_ space, but it's easy for an update to download multiple gigs, so something to keep in mind I guess.The size of the nix store has never really caused me issues, but ending up with a full `/boot` has. I now always set `boot.loader.systemd-boot.configurationLimit` to avoid these issues.
The plus side though if you go all in is that you can build once on something more powerful and just copy the updates to the low powered stuff.
I just went back to Bash and it honestly works just fine, any rough edges probably don't outweigh how inscrutable Nix tends to be: https://github.com/dbalatero/dotfiles/blob/main/apply
Not so fast! The hash story is actually much more complex than that, as this recent article showed: https://bernsteinbear.com/blog/nix-by-hand/
Regardless, the hash function is irrelevant to end-users, I just thought it was interesting that they cooked up such a convoluted scheme.
That said I wish it was easier to be able to pick a specific build of for example ruby like ruby-3.3.7-p123.
I had my time trying every new distro that would be popping up on Linux related magazines CD/DVDs up to the mid-2000's (they still do in some European countries), unless it comes on a laptop I can get at the mall computer store, or some customer imposes it on our delivery contract, not really something I want to give a chance to.
Me, too! That was a blessed part of my childhood and adolescence.
You're not entirely wrong about the comparison to Gentoo, but I do think NixOS is more practical than Gentoo.
It's happening: Companies are adopting Nix [0] and you may see such requirement come up some time.
I am yet to see any classical VM deployment isn't one variation of Debian, Ubuntu, Red-Hat, SuSE.
Note how that list has a certain startup feeling to the company list, not boring big corp.
Would I use nix for my servers? Yes. Would I also use nix personally to keep my systems in sync? Yes. Will I recommend nix to others? Absolutely not.
Put differently, doesn't the fundamental complexity of Nix come down to the combination of (a) "every app should get its own dependencies" and (b) "don't include dependencies with each app" ?
I don’t use Nix, but I’ve played with other Linux package managers enough to understand the desire for something better, and something that’s actually predictable, especially (emphasis here!) when mixing/matching arbitrary versions of dependencies. I used Gentoo/Gentoo-derivates for several years and it (building random packages with == specific versions) is a sticky problem when using a traditional build system.
Of course, once the packages are built then you can do whatever hacks to bundle the dependencies with it. But the hard part is building them in a consistent, “reproducible” way.
Both the macOS .app design, and various ad-hoc Linux packaging strategies deal with the deployment step in a relatively trivial, extremely non-invasive way.
The build step is something else entirely, and while I can imagine that for some projects what Nix offers has much value, for others the problem is solved by a permanently deployed build stack (or stacks) whose specificity goes beyond anything that a distro-managed system could offer.
[0]: https://coder.com
Nix is in a bit of a split/argument/fork-prone thing at the moment but it looks like this will settle into a practical/purist thing where everyone can be happy, so go in knowing which crowd is your people.
I'm not a purist so listen to one of them if you are. That said:
- flakes aren't optional or experimental, they're the default, its a small clique holding it up for years and its getting forked around by everyone serious
- flake parts is looking like a lock for the de facto flake standard, something else might come along but the leet stuff is congregating around it
- Cachix devenv is a good gateway drug: its better than Docker and easier to graduate from when it gets serious on that component
- nix-ld and FHS envs aren't a sin, they fix most all the friction bilugs and should be the default design, I overlay the shebang thing and all the unfree restrictions and all of it: the incompatible by design people are aging out, this will all work flawlessly in a year or two and you can do it today but its inside baseball
If you need to support Mac and Linux without the limitations of a strict all-OCI flow it's your best option, if you need a chance in hell against serious supply-chain attacks its your best starting point. If you need to run on a mix of metal and cloud and/or double-virtualizing is an issue its The Way.
The best super accessible resource to go from zero to "holy shit computers can do that" is vimjoyer's YouTube.
Use sops.nix and your secrets management is solved. Also by Mic92 is nixos-generators, that is basically infinitely portable to anything. juspay's GitHub, srid's GitHub, pretty much all the nixos-asia stuff is galaxy brain next level. It all works great on MacOS if you're not over it yet with the telemetry.
(sidebar: telemetry is your biggest computer problem now and your phone addiction is part of the telemetry deal. Linux and Graphene and NextDNS and Proton and stuff are worth it even for Grandma finally. I'm glad the purists do their thing, its an act of service).
> It's the choice of people who can't compromise on anything (Anduril, more and more trading shops, shit like that).
This is crucial- Nix has interhent advantages in certain industries where it IS genuinely easier than any alternatives. We're gonna see adoption start there and disseminate to general tech companies in a more consumerized form IMO
apt-get install <package name>=<version>
> And frankly, no one really needs Kubernetes, they just have it because everyone and their grandma has it. I digress, that’s a topic for another day.
Oh grow up, ffs.
That's still not reproducible unless you use snapshot.debian.org or snapshot.ubuntu.com as upstream package index.
Go fill your boots if you want to use NIX, power to you, but be honest about why if you're going to write a piece about it.
1) you can python environment with dependencies! (can do that without nix)
2) you can pin dependencies by hash! (can be done with any tool mentioned in article: docker, pip, npm etc)
If you can't make reproducible dev environment in 2025 - it is 100% skill issue.
There's a lot to be said for having a single lockfile for all of them.
If I had to work with such mess of a project - I wouldnt bother with package managers at all. I just download and commit all dependencies to repo.
If dependencies are not supposed to change anyway - what is the point of downloading them from all over internet each time?
I'm specifically interested in how you handle packages that use C extensions that link system libraries.