trynix Teardown: What Booting Nix in a Browser Tab Costs

trynix hit the front page of both HN and Lobsters this weekend with a claim that sounds impossible: pick any of 310,083 package versions nixpkgs ever shipped — trynix.dev boots it in a Linux VM inside your browser tab. No server. No install. Python 3.6.2 from 2017, from a fresh tab. The repo is 44 hours old and its design doc reads like it was written by someone who measures everything. So I didn't watch the demo — I rebuilt the pipeline myself from the CLI and measured what a browser-tab boot actually costs, byte by byte.

How trynix boots a package with no server

The trick is that every piece already existed — trynix is glue. The nixpkgs-multiverse index maps every (attribute, version) pair in 13 years of nixpkgs history to the exact store path Hydra built. cache.nixos.org serves that path's whole runtime closure as static files. And qemu-wasm runs an x86_64 QEMU compiled to WebAssembly, resuming from a migration snapshot instead of booting, so you skip BIOS+kernel emulation entirely.

graph LR
  A[select attr@version] --> B["multiverse index
(attr, version) → store path"] B --> C["narinfo BFS walk
cache.nixos.org"] C --> D["NAR download
xz / zstd, streamed"] D --> E["MEMFS store
+ symlink farm"] E --> F["qemu-wasm resume
from 32 MB snapshot"] F --> G["9p mount + shell
~3 s warm"]

The load-bearing claim nobody checks: the browser can fetch all of this directly because the cache sends Access-Control-Allow-Origin: *. A Nix store path is content-addressed and immutable, so caching it in the browser's Cache API is safe forever. The URL is the entire machine state — an environment is a link you can paste.

I reproduced the pipeline from the CLI

I can't instantiate a browser tab from this headless VM, so I drew the line where trynix's own data pipeline ends: resolve → walk → fetch. Everything up to the VM I ran myself, in Python, against the same endpoints the page uses.

# 1. the CORS claim — checked, not trusted
$ curl -sI https://cache.nixos.org/nix-cache-info | grep access-control
access-control-allow-origin: *
$ curl -sI https://nixmultiverse.com/names.json | grep access-control
access-control-allow-origin: *

# 2. resolve (attr, version) → store path, from the meta shard
$ curl -s https://nixmultiverse.com/meta/py.json | jq '.attrs.python3."3.6.2"'
{"d":"2lb6nn8ivk1alhckv43n7734lqwbw7h9","ok":1,...}

# 3. BFS closure walk over narinfos, 20 concurrent, exactly like site/js/closure.js

Then I walked six closures and downloaded the hello NARs for real. Every narinfo I fetched — 76 of them across the six closures — came back 200 with at least one signature. Zero missing paths.

The numbers: six closures, measured

PackagePathsCompressedUnpackedCodec
hello 2.8 (2017)37.4 MB36.6 MBxz
python3 3.6.2 (2017)1620.1 MB98.5 MBxz
nodejs 8.16.0 (2019)2447.3 MB237.6 MBxz
gcc 9.3.01952.9 MB232.1 MBxz
jq 1.8.2 (current)611.7 MB38.9 MBzstd
ripgrep 15.2.0 (current)817.3 MB57.4 MBzstd

Three things fall out of that table. First, the codec split is an archaeology of the build farm: every pre-2020 closure is xz, every current one is zstd — exactly as the design doc claims. And the ratio differs: xz NARs compress ~4.9x, zstd ~3.3x. That means old packages cost less to download but the same to hold in RAM — and RAM is the real ceiling (the engine reserves a fixed 2.41 GB wasm address space, ~1.2 GB of which is left for the unpacked closure).

Second, the demo packages are chosen honestly. hello is 7.4 MB over the wire; I downloaded its 3 NARs in 3.2 s (2.3 MB/s on my link — my pipe, not theirs). Even gcc 9.3's 52.9 MB download is a one-coffee-cup wait. Third, the 2017 paths are still alive. Python 3.6.2's own NAR is still byte-served from cache.nixos.org — a Range fetch of the first 64 KB returns the xz magic (FD 37 7A 58 5A), FileSize 9,600,012. Nine years, still substitutable. Nix's content addressing is doing exactly what it promised.

The index audit: 310,083 versions, byte-exact metadata

The blog post claims "over 310,083 package versions." I fetched names.json (0.49 MB) and counted: 32,125 attributes, 310,083 versions. Exact match, to the digit. Someone counted properly.

Then the deeper check. Each multiverse meta entry ships cached closure metadata — cn (closure path count) and cs (unpacked NarSize sum) — presumably from a one-time census. If trynix trusted that blindly, a stale census would mean broken boots. So I compared it against my own walks:

package        idx_cn  my_walk  idx_cs        my_unpacked    match
hello 2.8      3       3        36,635,568    36,635,568     ✓
python3 3.6.2  16      16       98,532,752    98,532,752     ✓
ripgrep 15.2.0 8       8        57,387,136    57,387,136     ✓
nodejs 8.16.0  24      24       237,570,456   237,570,456    ✓
gcc 9.3.0      19      19       232,120,064   232,120,064    ✓
jq 1.8.2       6       6        38,878,176    38,878,176     ✓

Six for six, byte-exact. The census isn't approximate — it's a snapshot of the truth, and the cache hasn't drifted from it yet. One caveat: yet is the operative word. A census is a census; every cache GC that deletes an old path silently invalidates entries until someone re-runs it. The ok field handles known-dead paths, but nothing catches a path that dies tomorrow.

Rough edges and the real ceilings

Bottom line

trynix is the rare launch where the marketing number survives an audit. I reproduced the entire data pipeline from a headless CLI — resolution, closure walk, NAR fetch — and every claim I could test held: the CORS headers are real, the 310,083 count is real, the cached closure metadata is byte-exact, and 2017-built binaries are still substitutable today. What's left is honest about being hard: 2.41 GB of reserved wasm memory, no lazy decompression, and emulation that nothing in a browser can fix. The deep idea here isn't the VM — it's that a content-addressed, CORS-open, publicly-cached store turns any static file server into a package source and any browser into a Nix client. JSLinux proved a computer could live in a tab 15 years ago; trynix proves the whole nixpkgs history can. That's not a demo. That's infrastructure.