Where can I get a statically built nix?

Matthew Bauer made a blog post about this nix and it’s exactly what I’m looking for but his post is a few years old.

There have been mentions of a staticaly built nix in multiple places ( for example Nix Portable: Nix - Static, Permissionless, Install-free, Pre-configured - #2 by fzakaria ) and it supposedly is possible in nixpkgs nix: allow static compilation by domenkozar · Pull Request #70024 · NixOS/nixpkgs · GitHub but the installation guide Install Nix — nix.dev documentation doesn’t mention anything about it nor how to do it.

The wiki Nix Installation Guide - NixOS Wiki points to a method using proot and/or user namespaces, but that means some programs have to be prefixed with nix-shell -p or nix run, while others natively on the host just work out of the box. This makes scripting hard and running pre-installed programs that search for dependencies in PATH impossible.

Anybody have a clue?

1 Like

The package is called nixStatic.

You can download it on any machine with flakes-enabled Nix, even if you’re not on x86_64-linux, like so:

$ nix build --print-out-paths nixpkgs#legacyPackages.x86_64-linux.nixStatic
/nix/store/6yawyssc3qycj5m3818cblnndpa8f5b6-nix-static-x86_64-unknown-linux-musl-2.15.1

The content of that directory is probably what you’re looking for.


If you don’t want to use flakes, you can also find the store path manually:

If you click on “Show more package details” and scroll down, you can see a list of platforms:

Clicking on “x86_64-linux” will lead you to the hydra page for this package, where you can select the latest build, then click the “Details” tab, which will show you the final output path:

Which you can just copy-paste into this command:

$ nix-store --realise /nix/store/m5c34ml3wbna241jgdf9vlwc1xzp0d1w-nix-static-x86_64-unknown-linux-musl-2.13.5
these 5 paths will be fetched (7.68 MiB download, 25.76 MiB unpacked):
  /nix/store/g0hmn34bf2q0w4f6c8521sx4y7w8fd6j-libxml2-static-x86_64-unknown-linux-musl-2.10.4
  /nix/store/m5c34ml3wbna241jgdf9vlwc1xzp0d1w-nix-static-x86_64-unknown-linux-musl-2.13.5
  /nix/store/m2dgyjz36c6yjcxnhhbr3scm698vas0c-nlohmann_json-static-x86_64-unknown-linux-musl-3.11.2
  /nix/store/4vn9iyljlnj0kn6gqlads4mfhl547dv6-openssl-static-x86_64-unknown-linux-musl-3.0.11-etc
  /nix/store/a4bk0fhq67ih6y6kzm0si7a8qzyh21vf-zlib-static-x86_64-unknown-linux-musl-1.2.13
copying path '/nix/store/m2dgyjz36c6yjcxnhhbr3scm698vas0c-nlohmann_json-static-x86_64-unknown-linux-musl-3.11.2' from 'https://cache.nixos.org'...
copying path '/nix/store/4vn9iyljlnj0kn6gqlads4mfhl547dv6-openssl-static-x86_64-unknown-linux-musl-3.0.11-etc' from 'https://cache.nixos.org'...
copying path '/nix/store/a4bk0fhq67ih6y6kzm0si7a8qzyh21vf-zlib-static-x86_64-unknown-linux-musl-1.2.13' from 'https://cache.nixos.org'...
copying path '/nix/store/g0hmn34bf2q0w4f6c8521sx4y7w8fd6j-libxml2-static-x86_64-unknown-linux-musl-2.10.4' from 'https://cache.nixos.org'...
copying path '/nix/store/m5c34ml3wbna241jgdf9vlwc1xzp0d1w-nix-static-x86_64-unknown-linux-musl-2.13.5' from 'https://cache.nixos.org'...
warning: you did not specify '--add-root'; the result might be removed by the garbage collector
/nix/store/m5c34ml3wbna241jgdf9vlwc1xzp0d1w-nix-static-x86_64-unknown-linux-musl-2.13.5
3 Likes

Did you try the steps listed in [wip] Statically built Nix by matthewbauer · Pull Request #56281 · NixOS/nixpkgs · GitHub already ?

How would I do that without nix installed? Matthew’s blog post didn’t require it and the hydra build doesn’t seem to indicate where exactly its output is stored nor how long it’ll be available. So if I went off and wrote wget -O ~/bin/nix $url into a script, that might break, I assume.

Yeah, but unsurprisingly it fails as the repo, URL for download, and blog entry are 4 years old.

Nix caches don’t serve store paths as regular .tar or .zip archives, but as .nar (Nix ARchive) files. There are a few reasons for that, but the main ones are that Nix requires symbolic links and execute bits to be preserved, while it doesn’t care about other info like timestamps.

TL;DR

  1. Build standalone nix-nar binary on a machine that has nix installed:
    nix build gitlab:abstract-binary/nix-nar-rs#static-x86_64-linux
    
  2. Put the resulting target/x86_64-unknown-linux-musl/release/nix-nar binary somewhere you can retrieve it (git repo, ftp server)
  3. Download it to your target machine (and chmod +x nix-nar)
  4. Download the static nix NAR file on the target:
    wget -O nix.nar.xz https://cache.nixos.org/nar/00iic1w1zq5amx1iz75k4k8qihh4jz40635xnnqsghkiwadpypnr.nar.xz
    
  5. Decompress it:
    xz -d nix.nar.xz
    
  6. Extract the nix binary from the NAR:
    ./nix-nar cat nix.nar /bin/nix > nix
    chmod +x nix
    
  7. Profit
    $ ./nix --version
    nix (Nix) 2.13.3
    

Anyway, let’s dive into a rabbit hole, shall we?

Generally, interacting with NARs should only be done by nix commands themselves, and downloading something directly from a cache without nix is not fun.

But for academic interest, let’s see how far we can go!

First off, to understand what’s going on, I took a look at the nix-serve source code, which is extremely short, less than 100 lines. I don’t know if this is the script that runs cache.nixos.org, but you can use it to create your own cache locally, so it needs to be somewhat compliant.

Additionally, I locally exported a downloaded derivation to a temporary file cache and took a look at that:

$ nix copy /nix/store/m5c34ml3wbna241jgdf9vlwc1xzp0d1w-nix-static-x86_64-unknown-linux-musl-2.13.5 --to file:///tmp/cache
$ tree /tmp/cache
/tmp/cache
├── 4vn9iyljlnj0kn6gqlads4mfhl547dv6.narinfo
├── a4bk0fhq67ih6y6kzm0si7a8qzyh21vf.narinfo
├── g0hmn34bf2q0w4f6c8521sx4y7w8fd6j.narinfo
├── log
├── m2dgyjz36c6yjcxnhhbr3scm698vas0c.narinfo
├── m5c34ml3wbna241jgdf9vlwc1xzp0d1w.narinfo
├── nar
│   ├── 00ndpgx7l0p93h01ladririr9s4banp0ccwjqllni6lsazwvhsk9.nar.xz
│   ├── 03l8my59a6ybgxmqghsrm8zgs2wfv4vsfhizkh35bax9lwvq429k.nar.xz
│   ├── 0g5aad578bw9115v80naj3yyj5v3hhklsvgsmxmh9cisy1ms8rjm.nar.xz
│   ├── 0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz
│   └── 1vp2cpa9w1n08an22c60wsy42692ypgazxrks1fvrbzdvmz6v60p.nar.xz
├── nix-cache-info
└── realisations

4 directories, 11 files

This is basically the structure of cache.nixos.org.

And sure enough, when I take the hash from the output we care about, and curl it:

$ curl https://cache.nixos.org/m5c34ml3wbna241jgdf9vlwc1xzp0d1w.narinfo                                                                                                                                                                                                                                                              ✔ 
StorePath: /nix/store/m5c34ml3wbna241jgdf9vlwc1xzp0d1w-nix-static-x86_64-unknown-linux-musl-2.13.5
URL: nar/0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz
Compression: xz
FileHash: sha256:0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a
FileSize: 7337388
NarHash: sha256:0g6g4qmn9wfhq59jp3al7mx8q0v1h6hbf5z5p3c69mahlcpkk6kf
NarSize: 23630272
References: 4vn9iyljlnj0kn6gqlads4mfhl547dv6-openssl-static-x86_64-unknown-linux-musl-3.0.11-etc g0hmn34bf2q0w4f6c8521sx4y7w8fd6j-libxml2-static-x86_64-unknown-linux-musl-2.10.4 m2dgyjz36c6yjcxnhhbr3scm698vas0c-nlohmann_json-static-x86_64-unknown-linux-musl-3.11.2 m5c34ml3wbna241jgdf9vlwc1xzp0d1w-nix-static-x86_64-unknown-linux-musl-2.13.5
Deriver: z4s9qy45scy8w7z75qyaqf44y2a3zb0l-nix-static-x86_64-unknown-linux-musl-2.13.5.drv
Sig: cache.nixos.org-1:J7iAPhJoZ6dL0iASny3u3Yr1JY/9BWNnxqTnxwYJ/riygRvN+sHPZY7gS3spz2WLI02C0wYsNIDuPx2A2/7oAg==

Cool! And it even tells us the URL we need to download:

$ wget https://cache.nixos.org/nar/0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz
--2023-10-17 20:53:22--  https://cache.nixos.org/nar/0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz
Loaded CA certificate '/etc/ssl/certs/ca-certificates.crt'
Resolving cache.nixos.org (cache.nixos.org)... 2a04:4e42:6f::729, 199.232.190.217
Connecting to cache.nixos.org (cache.nixos.org)|2a04:4e42:6f::729|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7337388 (7.0M) [application/x-nix-nar]
Saving to: ‘0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz’

0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz 100%[===============>]   7.00M  9.67MB/s    in 0.7s    

2023-10-17 20:53:23 (9.67 MB/s) - ‘0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz’ saved [7337388/7337388]

Now we can decompress this file and look at the result with nix nar:

$ xz -d 0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar.xz
$ nix nar ls 0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar /
./bin
./etc
./lib
./libexec
./share
$ nix nar ls 0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar /bin
./nix
./nix-build
./nix-channel
./nix-collect-garbage
./nix-copy-closure
./nix-daemon
./nix-env
./nix-hash
./nix-instantiate
./nix-prefetch-url
./nix-shell
./nix-store

And if you were to have nix installed, you could now also extract files from it, like the nix binary:

$ nix nar cat 0qm7zfi163sw36880gfbfdpk9d6j48ms0g8i1cwz6db8lqary96a.nar /bin/nix > nix
$ chmod +x nix
$ ./nix

So it feels as if we’re ever so close, and the NAR format is very simple and very stable, but you need something to read it. Luckily, there’s a dedicated rust utility called nix-nar-cli that you can use. You have to build it once, of course, but you can do that on a machine with Nix (or cargo):

nix build gitlab:abstract-binary/nix-nar-rs

The result/bin/nix-nar has the same interface as nix nar, so you can run

nix-nar cat $file /bin/nix > nix

to unpack it. I tried it out with the NAR above, and the result was binary-identical to nix nar, but I didn’t audit the code itself.

It is only 1.3MB big, so you could probably embedd it in a git repo or wherever else to bootstrap the download of nix. HOWEVER, this binary is not built statically by default either, so if you just try to copy it like that, it will fail to run with the very confusing error message:

bash: no such file or directory: ./nix-nar

So, let’s try to do that as well (assuming you already have rust):

$ rustup target add x86_64-unknown-linux-musl
info: downloading component 'rust-std' for 'x86_64-unknown-linux-musl'
info: installing component 'rust-std' for 'x86_64-unknown-linux-musl'
$ clone https://gitlab.com/abstract-binary/nix-nar-rs.git
Cloning into 'nix-nar-rs'...
Resolving deltas: 100% (187/187), done.
$ cd nix-nar-rs
# If you have direnv installed and get an error, do NOT run `direnv allow`!
$ cargo build --target=x86_64-unknown-linux-musl --release --workspace
$ target/x86_64-unknown-linux-musl/release/nix-nar --version
nix-nar-cli 0.3.0

The resulting output target/release/nix-nar is now a statically linked binary that you can easily copy to your target machine.

The nix-nar-rs repo has a flake that can be used for building as well, but it builds against glibc and links it dynamically, which will most likely cause the binary to not work if you just copy it to another machine.

I’m sure there’s some way to add an additional output to the flake, I might revisit that option at another point in time. For now, I added a Feature Request to the repo.

EDIT: I submitted a PR that was accepted, so now building a static binary is as simple as:

nix build gitlab:abstract-binary/nix-nar-rs#static-x86_64-linux

I updated the instructions accordingly.

3 Likes

It seems somewhat unfortunate that these are listed as dependencies here; they’re not actually used and the relevant binary is static.

If you already read my last post, you might want to read it again, I overhauled it with a fully working solution now!

I don’t know for how long cache.nixos.org retains artifacts, but AFAIK, especially the revisions that are publicly released are kept for a long time. If you take a look at releases.nixos.org, you can see the retained releases go all the way back to 2013. The page I linked is for one such branch, you can see that it says “nixos:release-23.05”, so everything in here will be retained for years to come. You might also be interested in this post that gives a breakdown of how many hundreds of terrabytes the cache contains.

If you want to be extra sure, you can go to the latest nixos release (currently nixos-23.05.997.ddf4688dc7a), download the store-paths.xz file, and search for the path there:

$ wget https://releases.nixos.org/nixos/23.05/nixos-23.05.997.ddf4688dc7a/store-paths.xz
$ xzcat store-paths.xz | grep nix-static
/nix/store/fri13aiy40j6smf1d1dsndwbc3178xvc-nix-static-x86_64-unknown-linux-musl-2.13.3
/nix/store/ilvh2g7gskjvvbm16gwwblf279h7lby4-nix-static-x86_64-unknown-linux-musl-2.13.3-dev

You can now use the hash of the path not ending in -dev when downloading the narinfo:

$ curl https://cache.nixos.org/fri13aiy40j6smf1d1dsndwbc3178xvc.narinfo
StorePath: /nix/store/fri13aiy40j6smf1d1dsndwbc3178xvc-nix-static-x86_64-unknown-linux-musl-2.13.3
URL: nar/00iic1w1zq5amx1iz75k4k8qihh4jz40635xnnqsghkiwadpypnr.nar.xz
[...]

In the TL;DR above, I already inserted that URL to be on the safe side :slight_smile:

Yeah good point, I haven’t investigated why that’s happening yet.

1 Like

I have installed static nix using nix-env -iA nixpkgs.nixStatic. How can I use it to statically compile programs in the Application library?

nixStatic is statically compiled itself, but that has no influence whatsoever on how programs compiled with nix-build or nix build are compiled. Please open a separate topic for this issue and describe what you’re trying to achieve in more detail there.

Thanks @iFreilicht for putting time into researching this. My current solution is a little smaller and uses docker

docker run --rm -v $(pwd):/app nixos/nix /app/pull_nix.sh

pull_nix.sh

#!/usr/bin/env bash
set -eux

nix-channel --update
nix-shell -p nixStatic --run 'cp $(which nix) /app/'

Nothing to build. No static URL.

But I do think that these kinds of workarounds shouldn’t even be necessary, so I created a feature request on github: [Feature request] Provide static binary on github release page · Issue #9176 · NixOS/nix · GitHub
Your research might come in handy if somebody wants to write a script or binary for pulling static nix should the feature request be rejected.

IMO, this should be a wget -o nix.tar $tarUrl && tar -xvf nix.tar && mv nix-*/nix . && rm -rf nix-*/ or even better wget -o nix $url.

3 Likes

Ah using docker is a clever solution. I wasn’t sure what tools you’d have installed on the target, so I tried to be as minimal as possible.

Having the static binary available for easy download is definitely a good idea, though, let’s continue the discussion on the feature request :slight_smile:

2 Likes

https://wh0.github.io/nix-cache-view/view.html?cache_base=https%3A%2F%2Fcache.nixos.org&hash=m5c34ml3wbna241jgdf9vlwc1xzp0d1w

I have a web tool that lets you get the binary from the cache

image

wait for your browser to download the .nar file, then expand the tree to the bin/nix file that you want

oh there’s also a “.ls” file served next to the .narinfo

https://cache.nixos.org/m5c34ml3wbna241jgdf9vlwc1xzp0d1w.ls

so you can get the offset+size of the bin.nix file in the decompressed .nar and do it with dd or whatever from the command line

2 Likes