I can haz smoller NixOS ISOs?
I can haz smoller NixOS ISOs?
I can haz smoller NixOS ISOs? 2026-06-19 :: Tech :: tags: #Deep Dive #ISO #Nix #NixOS #Virtualization
In which I painstakingly remove functionality from a Linux live image. Like a normal person. 在这篇文章中,我将费尽心思地从 Linux Live 镜像中移除功能。就像个正常人一样。
But first, I can haz ISO at all
首先,我能搞到 ISO 吗?
One of NixOS’ cooler party tricks is that you can trivially take some configuration and run it as a virtual machine (VM). nixos-rebuild build-vm will give you one for your system configuration. But you can also do it for any given configuration you want, system or not!
NixOS 最酷的“派对戏法”之一就是你可以轻松地获取某种配置并将其作为虚拟机 (VM) 运行。nixos-rebuild build-vm 可以为你当前的系统配置生成一个虚拟机。但你也可以对任何你想要的配置进行同样的操作,无论它是否是系统配置!
let pkgs = import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/567a49d1913ce81ac6e9582e3553dd90a955875f.tar.gz";
sha256 = "1vq77hlx8mi3z03pw2nf6r5h7473r1p9yxyf58ym3fh01zppmfln";
}) {};
in pkgs.nixos {
system.stateVersion = "26.05";
services.getty.autologinUser = "root";
}
That’s enough to get a minimal VM, you can run it yourself with $(nix-build basic-vm.nix --attr vm --log-format bar --no-out-link)/bin/run-nixos-vm. This will create a “thin” VM: there’s a disk image, but it only contains the files you’ve made yourself once you’re inside of the VM. Everything else (cough /nix/store) gets mounted in from your host OS instead.
这足以获得一个最小化的虚拟机,你可以通过 $(nix-build basic-vm.nix --attr vm --log-format bar --no-out-link)/bin/run-nixos-vm 自行运行它。这将创建一个“瘦”虚拟机:虽然有一个磁盘镜像,但它只包含你在虚拟机内部创建的文件。其他所有内容(咳咳,/nix/store)都是从宿主机操作系统挂载进来的。
And that’s great when it fits, but sometimes we just need something a bit more normal. Something we can just run in libvirt, or even ship off to some remote host that might not have Nix. It might not even run Linux at all. Hell, there might not even be a hypervisor! In short, I just want a damn ISO. 当这种方式适用时固然很好,但有时我们需要更“正常”一点的东西。比如可以在 libvirt 中运行的东西,或者可以发送到没有安装 Nix 的远程主机上的东西。它甚至可能根本不是运行 Linux 的。见鬼,甚至可能连虚拟机管理程序都没有!简而言之,我只是想要一个该死的 ISO。
Thankfully, NixOS still has my back here: 谢天谢地,NixOS 在这方面依然支持我:
let pkgs = import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/567a49d1913ce81ac6e9582e3553dd90a955875f.tar.gz";
sha256 = "1vq77hlx8mi3z03pw2nf6r5h7473r1p9yxyf58ym3fh01zppmfln";
}) {};
in pkgs.nixos ({ lib, ... }: {
system.stateVersion = "26.05";
services.getty.autologinUser = "root";
imports = [ "${pkgs.path}/nixos/modules/installer/cd-dvd/iso-image.nix" ];
image.baseName = lib.mkForce "nixos";
})
We’ll need to run QEMU ourselves this time, but it’s not too bad: 这次我们需要自己运行 QEMU,但这并不难:
qemu-system-x86_64 --cdrom $(nix-build basic-iso.nix --attr isoImage --log-format bar --no-out-link)/iso/nixos.iso -m 1G --accel kvm
Trouble in paradise
天堂里的麻烦
So we’re done here, right? But… we haven’t even touched the actual topic of the title! So, uh, about that… 所以我们搞定了,对吧?但是……我们甚至还没触及标题的真正主题!所以,呃,关于那个……
$ ls -lh $(nix-build basic-iso.nix --attr isoImage --log-format bar --no-out-link)/iso/nixos.iso
-r--r--r-- 1 root root 458M jan 1 1970 /nix/store/kg8mv6296hbhm8als26r400nj1s7ry1n-nixos.iso/iso/nixos.iso
Uh oh. 458MiB? And it doesn’t even do anything yet! 糟糕。458MiB?而且它现在什么功能都没有!
[root@nixos:~]# vim
-bash: vim: command not found
That’s almost 10x larger than what my childhood’s Damn Small Linux needed to run a surprisingly complete desktop environment! We’re probably not going to reach those levels, but surely there’s room to do at least a little better. For comparison Alpine’s VM ISO currently sits at around 66MiB. So I guess that’s a reasonable baseline to compare against. 这几乎是我童年时期 Damn Small Linux 运行一个令人惊讶的完整桌面环境所需大小的 10 倍!我们可能无法达到那种程度,但肯定还有改进的空间。作为对比,Alpine 的 VM ISO 目前大约在 66MiB 左右。所以我认为这是一个合理的对比基准。
What else even is there? Well, let’s have a look at what we’re paying for… 还有什么呢?好吧,让我们看看我们到底在为哪些东西买单……
$ sudo mount $(nix-build basic-iso.nix --attr isoImage --log-format bar --no-out-link)/iso/nixos.iso iso --mkdir
$ du iso --all --block-size=1M | sort -n | tail -n10
3 iso/isolinux
13 iso/boot/nix/store/92id8yn2g9kj7bskld42p222pmk3y3ms-linux-6.18.35
13 iso/boot/nix/store/92id8yn2g9kj7bskld42p222pmk3y3ms-linux-6.18.35/bzImage
26 iso/boot/nix/store/zvn61hpw86ncvgsr8vjrfi3ahnk2c9hb-initrd-linux-6.18.35
26 iso/boot/nix/store/zvn61hpw86ncvgsr8vjrfi3ahnk2c9hb-initrd-linux-6.18.35/initrd
39 iso/boot
39 iso/boot/nix
39 iso/boot/nix/store
416 iso/nix-store.squashfs
458 iso
So.. 416MiB for the main userspace, 26MiB for the early boot environment, and 13MiB for the kernel itself. At least we can dismiss the latter two for now. So let’s crack that squash open too, and see what secrets it hides… 所以……416MiB 用于主用户空间,26MiB 用于早期启动环境,13MiB 用于内核本身。至少我们现在可以先忽略后两者。那么,让我们把那个 squash 文件也拆开,看看它隐藏了什么秘密……
$ sudo mount iso/nix-store.squashfs squash --mkdir
$ du squash --max-depth=1 --block-size=1M | sort -n | tail -n20
...
128 squash/60m4rxhg2fldqaak400c0lry96ijrzqn-python3-3.13.13
144 squash/f060awdpif3c41v4wbshd6h6jzbxdv66-linux-6.18.35-modules
1064 squash
Well, that looks an awful lot like a Nix store… And since we built it on the host machine, anything inside of it should be in our host store, too! So we can just replace squash/ with /nix/store/, and then use nix why-depends to find out where the dependencies come from! For example, we can ask where the Boost dependency comes from:
嗯,这看起来非常像一个 Nix store……而且既然我们是在宿主机上构建它的,里面的任何东西也应该在我们的宿主机 store 中!所以我们可以直接把 squash/ 替换为 /nix/store/,然后使用 nix why-depends 来找出这些依赖项是从哪里来的!例如,我们可以询问 Boost 依赖项是从哪里来的:
$ nix why-depends $(nix-build basic-iso.nix --attr config.isoImage.storeContents --no-out-link) /nix/store/qmajd4nyy8zf70g9p4x2lcq45gq5gzy3-boost-1.89.0 --precise
/nix/store/w27j1xagd7cb5qxm1phmvffdwnk4b3wc-nixos-system-nixos-26.11pre-git
└───activate: …fsx38qi-setup-etc.pl
/nix/store/7nvwsgl5cplzal9y5m9nnwjjrbbvzcjv-etc/etc..if (( _localstatus > 0…
→ /nix/store/7nvwsgl5cplzal9y5m9nnwjjrbbvzcjv-etc
└───etc/tmpfiles.d/00-nixos.conf -> /nix/store/0zl414yp4h3ppb63h20pljy5rn5v10w4-tmpfiles.d/00-nixos.conf
→ /nix/store/0zl414yp4h3ppb63h20pljy5rn5v10w4-tmpfiles.d
└───nix-daemon.conf -> /nix/store/hqwkw2nala59avjximpdmn1yi474