Once you’ve built software with Nix, the logical next step in many cases is getting it onto another device, such as a production server.
You typically do this using the nix copy command, which enables you to push the software to the target, or using a
But what if your target device isn’t connected to the network?
In that case, you can neither nix copy to it nor have it fetch from a binary cache.
Nix has long had a solution for this: nix-store --export and nix-store --import.
The first enables you to serialize a
nix-store --export $(nix-store -qR /nix/store/crrf4bysvarqp0g9pyhcrj47689g4i5l-hello-2.12.1) > fileThis can then be imported on another machine:
nix-store --import < fileBut these commands have a number of problems:
- They are memory inefficient, requiring at least as much RAM as the largest store path in the archive. If, for instance, the archive contains a 30 GB store path (think of an AI model), then importing the archive requires at least 30 GB of memory. For embedded devices, this can cause the import to outright fail with an out-of-memory error.
- They don’t support newer Nix features such as signatures and content-addressed paths.
- They don’t provide a way to see what’s in the archive.
- They have no equivalent in the new unified Nix CLI.
Enter nix nario
Determinate Nix 3.12.0 solves these problems by introducing a new nix nario command, where “nario” is simply the name we’re giving the file format produced by nix-store --export, which previously lacked a name.
Analogous to the cpio file format, a nario is essentially a concatenation of NAR (Nix Archive) files, one per store path, with some metadata.
Creating a nario is simple.
If you’re on NixOS, for example, you can serialize the entire current NixOS system closure (pointed to by /run/current-system) like this:
nix nario export \ --format 2 \ --recursive \ /run/current-system > system.narioThe flag --format 2 specifies that we want to use version 2 of the nario format.
Version 1 is the old format generated by nix-store --export; version 2 fixes the memory inefficiency issue and adds support for path metadata such as signatures.
We can now copy the nario file to another device, for example by copying it onto a USB stick. On the target device, we can import it into the local Nix store:
nix nario import < system.narioImporting this particular system closure takes just 40 MiB of RAM using nario version 2.
With version 1, using nix-store --import, it takes 2048 MiB!
If you’re wondering why that may be: this inefficiency is an inherent result of how version 1 is laid out.
Version 1 serializes store path contents (the NAR) before the store path itself, so deciding whether it can skip a store path requires Nix to first read the entire NAR into memory or write it to scratch space on disk.
The new format is also much faster when some store paths already exist. With version 1, for instance, repeating the import of the system closure above takes 7.3 seconds compared to 0.05 seconds with version 2.
A final feature worth mentioning is that nix nario can list the contents of a nario file, which was not possible with the old CLI:
nix nario list < system.narioThat displays a list of store paths and metadata like this:
/nix/store/4j6p91af1bfgnn31agg1c9ijr0kyg6gi-gcc-14.3.0-libgcc: 201848 bytes/nix/store/16hvpw4b3r05girazh4rnwbw0jgjkb4l-xgcc-14.3.0-libgcc: 201848 bytes.../nix/store/hb8nfbfxmlz9bq56nznvkcsb3c8f5cif-nixos-system-foo-25.05.20250925.25e53aa: 18968 bytesDespite nario not being a random-access format, this is quite fast: just 0.05 seconds for this 15 GiB closure.
We have an open pull request against NixOS/nix and hope to see this in upstream Nix soon.
Developer friendliness changes
nix nario brings a powerful new way to distribute software with Nix, but as with most of our releases, there’s some other fun stuff for Nix devs on offer.
nix flake clone now supports all flake references
The nix flake clone command now supports arbitrary input types.
Previously, it supported most types but in version 3.12.0 we’ve added the ability to clone tarball flakes, such as flakes published to
Relevant pull request
Print the Nix version when logging at the “talkative” level
When you run a Nix command, adding -vv sets the log level to “talkative,” which is useful for general debugging purposes.
Beginning with Determinate Nix version 3.12.0, that log level now prints the Nix version, which can be helpful in situations where knowing which Nix is responsible could prove decisive in tracking something down.
When using -vv, Determinate Nix now prints the Nix version. This is useful when diagnosing Nix problems from the debug output of a Nix run.
Relevant pull request
How to get Determinate Nix
If you already have Determinate Nix installed, you can upgrade to 3.12.0 with one Determinate Nixd command:
sudo determinate-nixd upgradeIf you don’t yet have Determinate Nix installed, you can upgrade or migrate to Determinate Nix on macOS using our graphical installer:
Install Determinate Nix on macOS now
Apple Silicon and Intel
On Linux:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ sh -s -- install --determinateOn NixOS, we recommend using our dedicated NixOS module or our NixOS ISO (NixOS installer for x86_64, NixOS installer for ARM) with Determinate Nix pre-installed.
On GitHub Actions:
on: pull_request: workflow_dispatch: push: branches: - main
jobs: nix-ci: runs-on: ubuntu-latest # Include this block to log in to FlakeHub and access private flakes permissions: id-token: write contents: read steps: - uses: actions/checkout@v5 - uses: DeterminateSystems/flake-checker-action@main - uses: DeterminateSystems/determinate-nix-action@v3 - uses: DeterminateSystems/flakehub-cache-action@main - run: nix flake checkIn Amazon Web Services:
data "aws_ami" "detsys_nixos" { most_recent = true owners = ["535002876703"]
filter { name = "name" values = ["determinate/nixos/epoch-1/*"] }
filter { name = "architecture" values = ["x86_64"] }}