Read

Running Arch Linux in your browser

A real Arch userland with pacman, bash and gcc, starting in seconds because its root filesystem is served file by file over 9p — and an honest account of what it cannot do.

A rolling-release distribution is the least likely thing to find in a browser tab. Arch has no fixed edition, no minimal spin and no installer that produces a small system: it produces whatever you told pacman to install, on a root filesystem that is a couple of gigabytes before you have done anything interesting with it. And yet it starts here in a few seconds, on an emulated 32-bit processor, having transferred a few megabytes. Both halves of that are worth explaining, because they should not be compatible.

Why this should not fit in a tab

Arch is assembled rather than shipped. There is no "minimal edition" to reach for: you install a base and then add what you want, and the result is however large you made it. The project's own notes for building a v86 image suggest a base install lands around 1.5 GB, and recommend a 10 GB disk if you intend to add anything. Against that, the machine you are given is a 32-bit PC with 512 MB of RAM whose every instruction is translated to WebAssembly and executed by a JIT — no hardware virtualisation, no shortcuts.

The naive way to put that in a browser would be to download a two-gigabyte disk image and boot it, which would take minutes of transfer before the first instruction ran and would fail outright on a slow connection. Nobody does that. The reason this machine works is that it never has a disk image at all.

The root filesystem is not a disk image

Every other machine in the catalog boots from an image: a file of raw 512-byte sectors that the emulator serves as an array. Arch does not have one. It mounts its root over 9p, the filesystem protocol from Plan 9, which the Linux kernel has spoken natively for years through the 9p and virtio drivers.

9p is small enough to describe in a paragraph. A client holds a fid, a 32-bit handle for a file it is interested in, and asks the server to walk that fid along a path, open it, read a range of bytes, clunk it when finished. The server answers each request with a qid that identifies the file uniquely. Linux speaks the 9P2000.L dialect, which adds the operations a POSIX kernel needs — getattr, setattr, readdir, symlinks — but the shape is unchanged. There is no superblock, no allocation bitmap and no on-disk layout at all, because there is no disk: the filesystem is a conversation (what a filesystem actually is).

basefs and baseurl

v86 serves that conversation out of two things. The basefs is a single JSON file — conventionally fs.json, generated by the project's fs2json.py — holding the entire directory tree: every name, its size, mode, owner and a content hash. The baseurl is a directory of the file contents themselves, stored under their SHA-256 hashes by copy-to-sha256.py. When the guest opens /usr/bin/python, the emulator finds the entry in the index and fetches exactly that one blob over HTTP.

So the only thing downloaded before boot is the index. Gigabytes of /usr/share/doc, locales and headers you never open are never transferred at all. The upstream v86 example configures the machine with a kernel command line that says as much, and pulls the kernel out of the 9p tree rather than shipping it separately:

// v86's arch example, in essence
new V86({
    memory_size: 512 * 1024 * 1024,
    vga_memory_size: 8 * 1024 * 1024,
    filesystem: {
        basefs: "../images/fs.json",   // the index: names, sizes, modes, hashes
        baseurl: "../images/arch/",    // contents, one file per SHA-256 hash
    },
    bzimage_initrd_from_filesystem: true,
    cmdline: "rw root=host9p rootfstype=9p " +
             "rootflags=trans=virtio,cache=loose init=/usr/bin/init-openrc",
});

# And inside a running guest, mounting another 9p share by hand:
mount -t 9p -o trans=virtio,version=9p2000.L host9p /mnt/9p

Two details in there are worth noticing. cache=loose tells the kernel it may cache aggressively and need not check coherency with the server, which is what makes the arrangement usable at emulated speed. And init=/usr/bin/init-openrc means this image does not boot systemd: bringing up a full service manager under emulation costs minutes, so upstream substitutes something that runs a handful of scripts and gives you a login.

Why it appears in seconds

A JSON index of a tree that size is measured in megabytes, and the first few hundred files the kernel actually opens are a few more. That is the whole cost of starting. Fetching files lazily explains why there is nothing to download, though it does not by itself explain why there is no boot to sit through. The machine here also restores a pre-booted snapshot — a recording of the emulator's entire state taken after somebody else already waited for the kernel, the 9p mount and init. You are not watching a fast boot. You are watching the absence of one.

The 32-bit question

Arch announced the end of i686 support on 8 November 2017, and the packages were gone from the mirrors by the end of that month. The emulated processor here is 32-bit, so upstream Arch as it exists today cannot run on it at all — not for want of memory or speed, but because nobody builds the packages.

What continues the line is Arch Linux 32, the community fork that took over when upstream stopped, and which still builds repositories for i486, i686 and pentium4 targets. So the system you are looking at is that lineage, or a userland preserved from around the transition: recognisably Arch, with the same directory layout, the same pacman and the same package database, but not the same distribution as the one on a modern desktop. Treat the version numbers as historical rather than current. The same story, told for every other distribution, is in Linux distros that run in a browser.

The first five minutes

There is no login ceremony and no desktop. You land on a root shell, and the useful first move is to confirm what you are actually sitting on:

[root@localhost ~]# uname -a
Linux localhost 5.15.0 #1 SMP i686 GNU/Linux

[root@localhost ~]# free -m
               total        used        free      shared  buff/cache
Mem:             494          43         385           0          66
Swap:              0           0           0

[root@localhost ~]# head -7 /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 3
model name      : 686
stepping        : 0
flags           : fpu vme de pse tsc msr pae cx8 apic sep mtrr pge cmov

[root@localhost ~]# ls /
bin  boot  dev  etc  home  lib  mnt  opt  proc  root  run  sbin  srv  sys
tmp  usr  var

[root@localhost ~]# pacman -Q | wc -l
271

[root@localhost ~]# head -4 /proc/interrupts
           CPU0
  0:      14822   XT-PIC  timer
  1:        231   XT-PIC  i8042
  9:          0   XT-PIC  acpi

Every line of that is worth a second look. model name : 686 is the kernel's fallback when the CPU offers no brand string — it prints the family number and gives up. XT-PIC in /proc/interrupts means the machine has no APIC and interrupts are being routed by a pair of emulated 8259s, with IRQ 0 counting up because the 8254 timer is ticking. free -m reports 494 MB rather than 512 because the kernel has reserved the difference. And pacman -Q answers instantly, because the package database is a handful of small files the 9p layer has already cached.

What works, what is slow, what does not

What you tryHow it goesWhy
bash, coreutils, grep, awk, viFine, near-instantSmall bursts of code the JIT has already compiled
Reading /proc, /sys, dmesgFineNo I/O at all — the kernel generates it
Man pages, find /usr, first-time readsNoticeably slow the first timeEach new file is an HTTP fetch over 9p
Starting pythonA few secondsHundreds of small opens, all of them 9p round trips
gcc on a 40-line C fileSeconds rather than millisecondsAn emulated CPU, and a cold JIT for each new binary
Compiling anything substantialDo notHours, if it finishes; a tab is not a build machine
X11 or any desktopPainful to unusableEvery pixel goes through an emulated VGA with no acceleration
pacman -SyuUsually impossibleNeeds guest networking through a relay — see below
Keeping your work after a reloadNoWrites live in JavaScript memory and go nowhere

pacman and the network problem

A browser tab cannot open a TCP socket to a mirror. It can open a WebSocket, so the standard answer in v86 is a relay: the guest's emulated network card — an NE2000 or a virtio device — hands its raw ethernet frames to the page, which tunnels them over a WebSocket to a proxy that puts them on a real network. Several exist; websockproxy and wsnic are the usual ones, and they need a TAP device and a DHCP server on the far end.

That is a server somebody has to run, and it is not something to rely on here. Assume there is no network. Even with one, pacman -Sy would have to reach an archlinux32 mirror that still carries packages matching a database from years ago, with signatures that still verify against a keyring of the same vintage — and a partial upgrade of an Arch system is the classic way to end up with a broken one. Treat the image as read-mostly: pacman -Q to see what is installed, pacman -Ql to see where a package put its files, pacman -Qo to find out what owns a file. Those all work offline and are the genuinely interesting half of the tool.

What it is actually good for

  • Reading a real Linux system. The filesystem hierarchy, what actually lives in /etc, why /usr/bin and /bin are the same directory now, what a package owns and what it does not. All of it in a machine you cannot damage permanently.
  • Watching the kernel talk about the hardware. dmesg here describes an emulated machine, so the messages line up with devices you can name: the 8259s, the 8254, the IDE controller, the virtio 9p transport (how x86 emulation works).
  • Shell work with the real tools. GNU coreutils, not busybox approximations — so the flags behave as documented and what you learn transfers to a machine that matters.
  • Small experiments in C or Python. Write it, compile it, run it, watch it segfault, work out why. Slow, but complete.
  • Trying something destructive. Delete /lib, rewrite /etc/passwd, unmount the root. The machine is disposable and the reset button is a page reload — which is also its main limitation.

Expectations

This is not a daily driver and nothing about it pretends to be. It is a real Arch userland running at a fraction of native speed inside a JIT inside a browser, on a distribution branch that upstream abandoned in 2017, with no reliable network and no persistence. What it gives you in exchange is a complete Linux system that costs nothing to start, nothing to install and nothing to throw away — available in the time it takes to open a tab. If you want something to keep, export the state before you leave, or start from the machine builder and build a system whose disk you control. Unfamiliar terms are in the glossary.