Nix-Powered Development with OCaml
dimitrije.website
dimitrije.website
I've followed the official docs, had to resort on Google et al. quite quickly to solve mundane problems (installing a channel is not on the default path of the docs for the new learner), and after much wandering and trial and error, I evenrially went to the discourse and asked what I should use between flakes, nix shell or develop... Every single answer was 1/extremely informative and nice 2/ crystal clear that Nix was in a confusing, in-between stage in its life cycle.
I very much appreciate the community and efforts but I work for a company that cannot afford the commitment that Nix requires right now. I will happily come back in 2 years and reevaluate.
1. What were the drivers and motivators for you to try Nix? What did you hope to achieve?
2. Was the current experimental status of the new nix CLI and flakes the primary cause of the confusion?
1. I'm leading some Platform Engineering projects at my company and one is focused on Developer Experience. We have a wide variety of configurations on the engineer's laptops and the development environments are not normalized. That causes a lot of noise and pain, as one knows. Nix is, or seems to be, an opportunity to take back control on how engineers run the applications locally.
2. I'd say it has many factors. The official documentation and guides are not yet suitable for a zero-knowledge newcomer. As an example, since channels are still recommended in the docs, the command to add the default channel is not in the "Install" page. Things go from `curl <(...) | bash` to `nix-shell`, and it fails (on a M2 Mac) unless I navigate somewhere else on the docs to find that I should add the default channel first.
Second point is about the terminology. Derivation, Flakes... are quite arcane terms. At least to the layman (me).
Third, community blogs etc are covering outdated versions, some rely on flakes, others not, it's understandle but also understandably disorienting, especially when you're after some guidance that the official docs could not provide.
Fourth, the API. `nix-shell` is not a shortcut for `nix shell`. Wait what?
---
When I'm saying I'll come back in 2 years, I mean that it is clear that the situation is changing rapidly and dust needs to settle. Maybe the direction is not clear yet, and that would be fine. My one and only advice would be to only ship a feature that is properly documented.
Reading something like "We recommend to use flakes, they're the future, but the APIs are in flux, but if you're wary and want stability, here are our stable APIs" would have saved me a ton of time.
How to piss off engineers in one statement. Developer machines are not something for you to “have control” over.
That's a force multiplier in dev & prod, not a hindrance.
That's exactly right - you want developers to set there machines up however they want. You also want a controlled enclave inside that, that is automatically set up and works consistently to make engineers lives easier and more consistent.
One approach people have taken for this is to use docker - a container with all the tools built in. Run the build commands inside this, problem solved. Using Nix achieves something similar, but with some benefits over the docker approach.
I've started using direnv to switch node versions for projects, based on already-existing CI configuration files, and it's fantastic. Having a full, working, stable development environment that can be shared among the team (amd future teams doing software archaeology when something breaks!) is my holy grail. I'm really excited about the future of nix.
But you're right that at the end of the day, devs should retain control. I think having clear, machine readable instructions for how to set up the environment enables that control. Forcing them to use the blessed config is not necessary. HAVING the config lets you know what you need to set up, your own way.
The only place such strict control needs to happen is in CI/CD, which just falls out of having the dev setup done.
Believe it or not, but the project emerges from the devs themselves, it was not even an idea before they spoke up and we had to make it a project, to support them.
1. Two independent use cases.
a) First was a personal use to carry my dotfiles/home config everywhere (office mac, home mac, virtual machines). Ended up configuring a flakes based home-manager setup.
b) Second is trying to get good devshell experience for the whole team. Especially in a monorepo setting with multiple languages and tools. Right now there is tons of setup required including installing right versions of java, python, nodejs, lots of instructions to get right python version, poetry install, setup aliases etc.
2. No, it is not. It didn't take much of research to conclude flakes are the way to go. Honestly, I am confused by so many responses about confusion between flakes and non-flakes as I don't think it take much effort to realize flakes are the way to go even if it is experimental.
Though, to be fair, it does hurt that two of the recommended resources - nix.dev and nix pills - do not cover flakes.
2) We're working on stabilization... just asking for a bit of patience.
> 1. What were the drivers and motivators for you to try Nix? What did you hope to achieve?
(a) Reproducible dev environments. I worked in a company back then had a big mix of different projects: Elixir, PHP, Java, JS, C#. We wanted everyone to just `cd` into a directory and have identical tools, versions of languages / DBs, everything.
(b) Ability to do transactional-like upgrades or downgrades of various pieces of software.
(c) Isolate software that usually pokes deep in the guts of the system or the user's home directory. PHP and JS were the main offenders here, especially with PHP on Linux (and Apache2 integration) you had to modify a good amount of files in various non-home-dir places. And JS is, well, JS, NPM's global `node_modules` directory is a horror to behold as we all know.
The experience was mixed. While we had one guy who was super invested in Nix and made sure to help us all install it and set it up, the day-to-day experience was lacking and people had to find ways to "repair" their setup after a few small upgrades of software. It felt like a chore. Sorry I can't give you more details but for us back then the main goal was to reduce the chores, not invent a different kind of them.
All in all, I'd say points (a) and (b) worked somewhat okay, while (c) we definitely didn't feel it did so we just migrated to Docker for some of our workflows.
> 2. Was the current experimental status of the new nix CLI and flakes the primary cause of the confusion?
Again, my take is outdated, but as a burned out senior dev my criticism boils down to the following:
I don't want to care about "flakes" at all. I want all tools to dispense with the cutesy pun-y terminology already and realize that people use them for work.
Give me one `nix` command with subcommands and good help outputs (and good centralized docs website). Reference: the `borg` backup program, also `git`. Before Nix gets into that state I'll not recommend it anyone.
---
I am in no way attacking you or disparaging yours and others' work but to me and my team back then it felt like Nix simply replaced one complexity with another. What drew us in was the promise of less complexity and that did not materialize.
I feel the general user experience is often lost on maintainers because they live and breathe their tool and to them the workflow is second nature. That's why I wrote this reply. Hopefully it brings some perspective.
Keep up the good work.
I wonder how many people would honestly admit "I see it mentioned in a lot of comments/headlines from articles posted on HN and I assumed if it is good enough to be mentioned, it'll probably solve a problem I didn't know I had"
"I hear about it a lot, I'm on my personal time, let's see what it's all about" seems very acceptable to me.
Not every good thing needs to be seen from an analytical perspective.
As a developer, in my personal activities, I don't draw the line. As a developer, in my work, I work with others to collectively agree to be on one aide or the other of the line. Even when we're retrospectively wrong. Sometimes, you can't know.
As a manager, I help engineers do the above.
I personally feel that Nix documentation and community blog posts sits on a gradient of theological meanderings about functional purity to actual praxis.
For one, I'm a systems engineer. What drew me to Nix was that I wanted a next-gen Ansible. A piece of software that can be used to reproduce a hosts OS state in an immutable way. For now, my homelab and laptop is all NixOS driven. I use deploy-rs and flakes to configure hosts. I can code but am not really a software engineer by trade. This is where I'm coming from.
Within the context of my established skills, Nix's documentation is awful. There were/are actions I want to accomplish that sit outside of the paradigm of Nix and when I try to accomplish those actions I feel lost.
For example, my main homelab machine has a Nomad server installed. The default Nomad package from Nix packages just installs the binary. So I needed to handle creating its data directory, its configuration, and laying down a systemd unit file. Where I got stuck was creating the data directory. I could accomplish the systemd configuration and the configuration file easily enough but I don't know how to create a directory outside of a derivation. I also don't know if a derivation is even needed.
But I also don't really know how to fit a derivation in my current flake setup so that it's processed when I change host state. I ended up solving the problem by having systemd manage a StateDirectory and creating the unit file/Nomad conf in configuration.nix for the machine. I also don't known if a derivation would even be what's needed. Do I use an overlay to extend the Nomad package? Can I ad-hoc create directories and then shove that into a module?
My point being is that system administrators, network engineers, release engineers, etc are all different types of engineers that would be interested in Nix... but they are NOT functional programming enthusiasts. I think Nix gets attention from a lot of people who are comfortable with Linux, package management, build processes, and configuration management systems like Ansible. But there is no clear runway for those types of engineers. Hell, NixOps seems to be an abandoned repo that has no documentation whatsoever for it's 2.0 release and there are dozen different projects for remote deployment to hosts.
If I took a sysadmin and told them "please codify OS state using Ansible" then they're likely to come back with a solution that could satisfy a technical need within an organization. If I took that same sysadmin and told them to use Nix to solve the same problem then they're likely to get lost for weeks.
It seems this is where a better description of what NixOS (and it's module system) is. My suspicion is that by starting with NixOS it is harder to be introduced to the simpler concepts. It is best to think of the NixOS modules as a domain-specific-language on top of Nixpkgs which is itself a large set of conventions on top of the fairly simple Nix language. I think people should be introduced from the bottom up.
> having systemd manage a StateDirectory and creating the unit file/Nomad conf in configuration.nix for the machine
Seems like you did get what you needed working; my best advice to understand that the key abstraction being created is the activation script. Perhaps we never clearly describe all of its moving parts and how it works?
> ... but they are NOT functional programming enthusiasts
I believe the key is for the Nix community messaging/marketing to focus less on the theoretical elegance and more on the practical benefits.
That's what everyone says about software that is just too complex.
I tried Nix for the first time yesterday and had a similar experience - it was just too buggy, too brittle, and too difficult. It's absolutely not worth it. The software was built to solve a difficult problem, and maybe it does that, but it became a difficult problem in itself.
For example, if you Ctrl-C while its downloading an update, you get in a weird state where nothing works. Ok, so just uninstall and reinstall right? Well, there's no uninstall. You have to manually delete everything and remove the /nix mount. Its 2023, why am I having to hunt all this down? Sure, if you reinstall it it tries to do that, but it fails every time, or it just misses things. I just don't have time for this. Nix is not even close to ready for production.
There is a new Nix installer from Determinate Systems, a company heavily invested in Nix, which should make installing Nix more atomic, reversible, and thus easier:
The Determinate Nix Installer https://news.ycombinator.com/item?id=34957953
And after getting pass the installation, Nix is great for production.
In the default multi-user mode, you can't even do that.
Heh? Oh you mean like during installing it for the very first time? Like, what do you expect from a bash script? Is bash not ready for production? That’s quite a strong statement when you didn’t even manage to install the thing… like, at least give it an honest try before you reach such a conclusion.
It can be put behind a confirmation Y/N terminal line as well.
But yeah, usually bash install scripts are almost never idempotent. I feel the maintainers of those scripts just give up and IMO they really shouldn't.
Almost all bash scripts aren't
Something else is wrong here. I've aborted updates hundreds of times without problems. Uodates are atomic and this shouldn't happen.
I'm hopeful that in two years, the community will be much more aligned around flakes, and the docs (at least the onboarding parts) will have a much clearer "happy path" oriented around accepted workflows like direnv and home-manager.
I'm not sure you're saying this to point fingers at the software or at the people, that is very confusing :D
That being said, Nix being in-between has been discussed in the Discourse of the project here about t supposedly simple: https://discourse.nixos.org/t/nix-shell-nix-shell-and-nix-de...
It was to me very interesting to see how insiders could be diverging and potentially not being fully aware of it.
Homebrew is an example of a simple piece of software. It just works.
But, it doesn't. I've had many packages fail to build from homebrew. Also there's a recent quote I remember:
> At some point I got frustrated with homebrew because I felt like it was spending too much time upgrading when I installed new packages, and so I thought – maybe I’ll try the nix package manager!
from https://jvns.ca/blog/2023/02/28/some-notes-on-using-nix/
> If you make a mistake, you can put it in a completely broken state. That is not a simple piece of software. Its overengineered and brittle.
Do you have any examples?
Previously, Nix installed its packages using 'channels'. You would add and update a channel, and then you could install packages from a channel. (This is roughly analogous to apt repositories). -- The CLI user experience for this channel-based approach is pretty terrible.
Nix flakes are a new feature where a project has a `flake.nix` file, which follows a standard format for declaring what packages a project provides. -- The CLI experience is much nicer, and has several other benefits over pre-flake Nix.
Flakes are still (for various reasons) labelled as "experimental". Much of the documentation has not been updated for flakes.
In 2 years, you'd hope that: the documentation improves, and there's less friction for adopting the technology.
Question to others: as someone with Nix experience and interested in learning OCaml but hasn’t quite bit the bullet, what does dune actually offer me if I’m using Nix to manage dependencies already? It feels like adding an unnecessary layer of LISP when Nix and opam should already cover everything.
I've used Nix/NixOS for years and still don't have a use case for flakes. I only enabled it this week to restore some functionality to the nix command, which is still a work in progress.
Getting there was painful and required a deep dive into the documentation. In the end, it was as simple as adding this line to my configuration.nix:
nix.settings.experimental-features = [ "nix-command" "flakes" ];
The flexibility of Nix is one of its main selling points, so it's impossible to say that "everyone" uses it in a similar way. Flakes are a welcome, but disruptive, enhancement. Hopefully, the experimental phase will end soon.If they haven't pinned nixpkgs, I have no idea at what point it _was_ building successfully. It's just dumb luck whether my system's channel is a compatible nixpkgs.
If it's pinned, great, I can use that pin until I'm bothered to bring it back to mainline. At that point I'll override the nixpkgs input and bear the cost of updating and maintaining it.
Flakes make managing pins those easier, through a nice(r) CLI, and a standard format for them.
That said, it feels like they are slowly coming to terms with it and just accepting it as default. Here are two examples of maintainers eventually accepting flake support on their repos after initial hesitation [1][2].
[1] https://github.com/tpwrules/nixos-apple-silicon/pull/47 [2] https://github.com/NixOS/mobile-nixos/pull/404
- Instead of channels, I use fetchGit with pinned revisions
- Instead of managing installed software via nix-env, I use buildEnv to collect everything I need in a single derivation. If I'm on NixOS I'll put that in systemPackages; or otherwise (e.g. on macOS) I'll use nix-env to only install that one package.
These have the advantage of being declarative files, which can be tracked in git; rather than imperative commands.
It's not in official documentation but the biggest reason is it's not reproducible by default and a new user could easily not pin nixpkgs, then think "wait... Nix isn't reproducible".
nix-shell and niv or otherwise pinning nixpkgs is pretty good, though there are some reproducibility advantages flakes has over nix-shell + niv such as:
- not allowing access to files not tracked in the git repo - forcing the user to lock their inputs with flake.lock whereas pinning/niv are optional
Also see: https://nixos.wiki/wiki/Flakes#Making_your_evaluations_pure
the "effort" is just pointing to a particular checkout from nixpkgs repo
> With flakes you can combine packages and versions explicitly.
it's the same thing with default.nix/shell.nix, just having a separate lockfile changes nothing in the underlying `builtins.fetchGit` / `builtins.fetchTarball`
In a team setting, enforcing that everyone has to follow purity best practices because nothing else is allowed is great.
I've found quite a few articles on alternatives to nix-shell in the flakes world, but I haven't found one for this usecase (which is also my main Nix usecase).
Particularly, how do I have a flakes equivalent for nix-shell that isn't pure? It is convenient to be able to run git, or SSH or whatever from my development shell, and those things shouldn't be in my project build setup.
The original Nix thesis [0] talks about using Nix as a low-level build system in Chapter 10.1. And the author of opam-nix* has made an attempt to use Nix as a build system [1]. However, dune is a complicated bit of software that's not easy to replicate, and Nix isn't particularly well suited to the task.
[0] https://edolstra.github.io/pubs/phd-thesis.pdf
[1] https://gitlab.com/balsoft/tumbleweed
*Which is essentially functionality to use Nix as a dependency manger with opam-repository.
purs-nix is a good example that even if per-module compilation is made into derivations, the rest of the remaining heavy-lifting of constraints resolution is done by the external build tool. You can think of Nix as a dependency manager, but only with a huge caveat that it doesn't actually manage constraints that come with those dependencies, especially transitive ones.
You'll still want dune, though I could see an argument for generating the dune files from nix expressions.
$ cat devenv.nix
{ config, ...}:
let
ocamlPackages = config.languages.ocaml.packages;
in {
languages.ocaml.enable = true;
packages = [ ocamlPackages.janeStreet.async ];
}
$ devenv shell
...
Recently also shipped 0.6 with container generation support: https://devenv.sh/containers/Let me know if you give it a try :)
https://github.com/NixOS/nixpkgs/pull/32946
If you find it important, I'd encourage opening a PR to ask for flambda to be enabled by default at https://github.com/NixOS/nixpkgs/issues/new/choose.
Especially if the ocaml community typically enables flambda by default these days.
let o = ocaml.override { flambdaSupport = true; }; in
let oP = ocaml-ng.mkOcamlPackages o (self: super: {}); in
oP.apron
[1] https://discourse.nixos.org/t/install-ocaml-flambda-compiler...If you're using opam then you can test flambda out by creating a new switch
opam switch create ocaml-flambda ocaml-variants.4.14.1+options ocaml-option-flambda
and then running "opam switch set ocaml-flambda". (You can replace 4.14.1 with your preferred version above.) image: nixos/nix:latest
before_script:
- nix-env -f shell.nix -i -A buildInputs
full_build:
stage: build
script:
- dune build
This uses a standard docker container with Nix built in. The before_script magic then installs everything into that container exactly as it is in your development environment. Subsequent commands then run exactly as in your environment.Of course with this approach, every CI job builds your whole environment. If this complex, and involves many compilation steps, then that is painful and you will instead want to use the nix setup to build a docker image that is used for your CI. For many projects involving small teams, that isn't necessary though and this works really well.
Using Flakes you can have a devShell, a check target and a build target neatly organized.
https://mynixos.com/pveierland/demo-ocaml/outputs
Run using:
nix develop https://api.mynixos.com/pveierland/demo-ocaml/archive/latest.tar.gzin my experience such changes are largely to accommodate nix's admittedly alien expectations (runtime dependencies living in the store, etc etc) and beyond that things behave more vanilla than almost any linux distro's repos
And getting management approval to build a packaging team so that you are not the only person In the company to go to, is a hard sell
This sounds more difficult than it is, the result will be a copied over binary file that has its “libc.so” and other dynamic libs replaced in the ELF-header with “/nix/store/hdjdewuieu737-libc/libc.so”. I recommend looking up a package in nixpkgs which has a similar install story, that’s the easiest way to write a new package.
In case you only want to run it locally https://github.com/thiagokokada/nix-alien and similar programs work fine with the binary.
I want to try NixOS, but the “minimal” image is over twice as big as my full (terminal only) development environment using Alpine Linux. I was kind of expecting NixOS to be closer in size to a glibc-based minimal distribution (after initial install)…
Do I have this wrong? Why doesn’t Ocaml just merge Base (with permission) and put their best foot forward for the ecosystem?
Meanwhile, the built-in Stdlib finally started to acquire much needed functions and modules during the late 4.x releases. My biggest worry is whether they'll keep up the pace, it doesn't even have a resizable array or, alternatively, a Clojure-like vector (but neither does Base).
May I also ask, have you worked with Ocaml for any medium to large projects?
I’d be curious to hear from major users that are NOT using Base, and hear how they’re getting on. The 5.0 release especially has catalyzed my interest in Ocaml, and I’ve a few years left in me still before retirement, I’d like to pick up one last niche ‘fore I quit.
The same thing is true with the build system dune and opam. Half-assed build system and package managers that we’re kind of stuck with.
I dream of a new cargo-like build+package manager system for OCaml that would just take over everything we currently have.
the Jane Street side are quite prolific with blog posts etc
as a newcomer to OCaml one of the first, and nicer-looking, intro resources you'll likely encounter is the Real World OCaml book https://dev.realworldocaml.org/ which unfortunately does everything using Base instead of the stdlib
Personally that didn't sit right to me and I prefer to use the stdlib by default (which seems fine and not in need of a wholesale replacement)
Thanks in advance.