4 ms·
Nix is badly named, because it is many things. Nix is a lazy, functional programming language that was designed to write reproducible packages (in the traditio
by ratorx 4y ago
Nix is badly named, because it is many things.
Nix is a lazy, functional programming language that was designed to write reproducible packages (in the traditional Linux distro sense).
A Nix package is just a function. It takes in some inputs (the dependencies), and produces an output. But instead of saying “take libssl”, you actually say “take libssl at this exact version” (or more precisely at this exact combination of input files with this exact content). However, because it is a programming language, you can make the package inputs generic over the version. This may seem esoteric and pointless but actually gives rise to a lot of nice properties (reproducibility, caching, recompilation tracking among others).
Nix Packages, are a collection of packages that are written using this language. It’s pretty comprehensive and many packages can be used on both Linux and Mac (like Homebrew), so it’s a cross-platform package manager. Unfortunately, the package manager for this set of packages is also called Nix.
NixOS is an operating system built on top of the language, using the packaging ecosystem (Nix Packages) and also providing some abstractions for writing services, declaring users, crons etc. (kind of like Ansible).
A Nix Flake is a relatively new thing. It is a framework on top of the Nix language for writing these {packages/OS config} that can much more cleanly specify the dependencies and pin them. Kind of like NPM, but for everything (package versions, OS module versions, any Git repo etc.). You could do this before, but it was less nice. Now, you can update all the dependencies of your entire project with one command, including things like shell plugins, Vim plugins, the version do Bash used to run your build scripts, developer tools, LSPs for your editor, your text editor itself (though I usually don’t go this far) etc.
There are many other uses. You can use it to build OCI images (they are just a specific way of combining some dependencies, and this can be expressed pretty easily in Nix). These have better properties than typical Docker images (built via Dockerfile) and can be cached better and tend to be smaller without needing to use something like musl. You can use it manage user dotfiles (similar to OS management, see Home Manager), developer environments (kind of like Python virtualenv, but for everything), project dependencies and whatever else you can build on top of the language really. Basically anything that could benefit from having packages nicely integrated with config could be built with/on top of Nix.
Why would you use it? Well for me:
* I like being able to manage package versions and config together painlessly, which was always a pain with Ansible.
* It’s fully declarative (if you remove the config for a service it stops running; not true for e.g. Ansible)
* Nix language is not the greatest, but it is sooo much better than YAML. Unfortunately the set of language features needed is quite niche, so it couldn’t just be a library for an existing popular language (except maybe Haskell or a Lisp)
* I like being able to update everything (with free rollbacks if something breaks) with 1 command.
* It’s pretty interesting. It’s theoretically quite well built and find ways to make good use of some nice programming constructs (e.g. fixed points, laziness), that appeals to me. What the language lacks is documentation and tooling.
* Reproducibility is nice, but not the selling point for me.
I dipped in my toes with using Nix Shell to have reproducible developer environments and then slowly moved everything over.
- MuffinFlavored 4y agoCould you help me understand how I'd migrate say... a Rust app with a Dockerfile running in Postgres glued together Docker Compose with Nix? Thanks for the writeup by the way, it's appreciated and well done. I get that NixOS is a Linux distro. What is Nix's answer for containers? aka the typical Docker flow Dockerfile -> build OCI image -> run it as a container Nix package -> ? -> ?
- ratorx 4y agoThere’s a few different levels of commitment, I’ll go through the just “dip your toes” variant to the fully commuted version: * Use Nix shell for developer environment. Use it to provide a version of Docker, Rust, development Postgres etc. This is just a kind of virtualenv you activate after you cd into the project directory. You can manually manage the versions of the tooling for your project separately (if needed) and you don’t need to install these globally on your developer machine. * Replace Dockerfile with a Nix package. This may be a bit trickier depending on how complicated the app is. You can build the rust app with Nix and then build a Docker image from the rust app and all other things you want in the container. You can do the same for Postgres (although there’s less point, since the Docker image is officially maintained). There’s no direct benefit to doing this (except maybe smaller images if you aren’t using e.g. alpine and maybe slightly better cacheability). * Replace your base OS with NixOS. This is whatever you run the Docker containers on. You’d still use Docker containers and Docker compose, but the underlying OS would be NixOS, rather than Ubuntu/Debian/etc. * Replace Docker containers with systemd services on NixOS. This is the hardest sell, but a lot of people use Docker just for versioning and providing dependencies, and you can just use Nix for that. So if you don’t need to use Docker for other things, you can just run apps on bare NixOS, using systemd configured with Nix (the language). That’s what using services on NixOS will do. You’ll basically be writing systemd services for your app (there is already going to be one provided by NixOS for Postgres) in the Nix language and running them directly on Linux. One thing that’s not easy to do with Docker Compose is cron jobs (for backups monitoring etc.). You can run these just as systemd timers. This also has some nice benefits (e.g. user isolation whereas Docker containers run as root, Unix sockets to avoid needing to bind ports and restrict access to certain users, no extra (runtime) layers on top of your OS, less complex networking). To illustrate as examples on the Docker flow Start: Docker, Rust etc. installed globally. Dockerfile -> build OCI image -> run it as a container on Debian/whatever as distro Stage 1: Docker, Rust etc. installed in a Nix Shell Dockerfile -> build OCI image -> run it as a container on Debian/whatever as distro Stage 2: Docker, Rust etc. installed in a Nix Shell Nix package -> build OCI image with Nix -> run it as a container on Debian/whatever as distro Stage 3 (can be done independently of 1 and 2): Docker, Rust etc. installed in a Nix Shell Nix package -> build OCI image with Nix -> run it as a container on NixOS Stage 4: Docker, Rust etc. installed in a Nix Shell Nix package running as a systemd service managed by NixOS