Skip to content

docs: mounting into a VM guest - #8

Merged
pi0 merged 3 commits into
mainfrom
docs/vm-9p
Jul 29, 2026
Merged

pi0 merged 3 commits into
mainfrom
docs/vm-9p

Conversation

@pi0x

@pi0x pi0x commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Serve a driver on the host, mount it inside a VM with one command and nothing installed in the guest. New page at /guide/vms, slotted in after Mounting.

The guide index already described 9P as serving "the local kernel, or a VM guest" — this is that page.

QEMU → 9P

The recipe is three commands, and the reason it is that short is worth stating: QEMU's default user-mode networking already routes 10.0.2.2 to the host's loopback. So there is no tap device, no bridge, no host-side ip command and no root on the host, and the server stays bound to 127.0.0.1 where nothing on the network can reach it.

mount -t 9p -o trans=tcp,port=5640,version=9p2000.L 10.0.2.2 /mnt/x

Verified end to end against a stock Alpine 3.21.3 virt guest (kernel 6.12.13-0-virt) driven over the serial console:

  • mounts with those three options and no modprobe — mount -t 9p autoloads v9fs/9pnet_fd
  • ls, read, write, mkdir -p, 16 MiB dd both directions, mv across directories, rm, df -T, umount
  • the mount table shows no msize=, which is the same proof-by-omission the local-mount notes already rely on; asking for msize=1048576 makes it appear
  • the minimal guest-side network setup is one line, since the host is on-link — no default route needed for the mount

The page also separates this from QEMU's own -virtfs, which exports a host directory through QEMU's built-in 9P server. Every -fsdev backend is directory-based (-fsdev sock,… → fsdriver sock not found), so there is no way to point virtio-9p at an external server — TCP is genuinely the route, not a fallback.

Firecracker → NFSv4.1

Firecracker cannot do 9P, for two independent reasons, and the page says so plainly rather than offering a recipe that cannot work:

  • it emulates only virtio-net/block/vsock (plus balloon and rng) — there is no virtio-9p device
  • its published guest kernels ship # CONFIG_NET_9P is not set

What those kernels do carry is CONFIG_NFS_V4=y / CONFIG_NFS_V4_1=y and # CONFIG_NFS_V3 is not set — so the transport is NFSv4.1 specifically, the half that landed in #7.

NFSv4.1 now has a real kernel client

AGENTS.md listed NFSv4.1 as having no real-client verification, for want of a mount.nfs on this host. A VM guest supplies one. The same server was mounted vers=4.1 from a real Linux kernel NFSv4.1 client and passed the same workload — mount, read, write, mkdir, 16 MiB dd, rename, unlink, unmount — with no rpcbind and no MOUNT program involved. AGENTS.md is amended accordingly.

This is still not a Tier-2 test column: the host itself has no mount.nfs, so a v4.1 mount test would have to carry a VM, which is a bigger decision than this PR.

Not verified, and marked as such

The Firecracker tap wiring has not been exercised end to end — this container has no /dev/net/tun, and every Firecracker network is a tap device. The kernel-config and device facts are read from Firecracker's own published kernels and docs, and the NFSv4.1 mount itself was verified from a QEMU guest (which needs no tap). The page carries a note saying exactly this, and it can come out once the run happens.

Notes

  • No perf numbers published — that stays restricted to .agents/benchmarks.md.
  • Guide pages renumbered to slot the new one after Mounting; URLs are unaffected, since the numeric prefix is routing-only. Confirmed against the built output: tuning and troubleshooting are unchanged and vms is new.
  • .agents/environment.md records the reproduction details, including the two container limits that shaped this (/dev/kvm needs sudo here; /dev/net/tun is absent).
  • pnpm test: 58 files passed / 4 skipped, 2779 tests passed. Docs site builds clean (22 pages).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mountx Ready Ready Preview Jul 29, 2026 11:51pm

Request Review

@pi0x

pi0x commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

Firecracker is verified now — and it needs less than the page claimed

A real Firecracker v1.16.1 microVM (guest kernel 6.1.102, their own ubuntu-22.04 root image) mounted this NFSv4.1 server and passed the same workload as the QEMU columns: ls, read, write, mkdir -p, 16 MiB dd both directions, rename, unlink, umount.

172.20.0.1:/ /mnt/x nfs4 rw,relatime,vers=4.1,rsize=1047672,wsize=1047532,namlen=255,hard,
proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.20.0.2,local_lock=none,addr=172.20.0.1 0 0

The "not witnessed" note is gone, and the section now carries the tap setup, a complete vm.json and the mount that was actually run.

Two findings that changed the page

No nfs-common in the guest. which mount.nfs mount.nfs4 finds nothing in Firecracker's root image, and the mount works anyway — with addr= and clientaddr= given explicitly there is nothing left for the helper to resolve, so mount(8) falls through to the mount(2) syscall. That is the difference between "rebuild your image" and "nothing to install", so the page now spells both options out on purpose rather than incidentally.

The guest needs no network commands at all, given an ip= kernel boot argument — the kernel configures eth0 before userspace starts.

On the container restart

Not needed in the end. /dev/net/tun is present, but CAP_NET_ADMIN is still absent from the bounding set (CapEff: 00000000a82425fb decodes to cap_sys_admin + cap_net_raw, no cap_net_admin), so ip tuntap add answers ioctl(TUNSETIFF): Operation not permitted even under sudo.

A new user namespace grants a full capability set (000001ffffffffff) over the network namespace it owns, so the tap, the mountx server and the VM all run inside one unshare -Urn --map-root-user and the guest never touches the container's real network. Recorded in .agents/environment.md along with the two gotchas (--map-root-user is required, and assets must live outside the drwx------ session scratchpad).

pnpm test: 58 files passed / 4 skipped, 2780 passed. Docs build clean.

pi0 and others added 3 commits July 29, 2026 23:49
Adds `docs/1.guide/5.vms.md`: serve a driver on the host, mount it inside
a VM with one command and nothing installed in the guest.

QEMU gets 9P over `trans=tcp`. Its default user-mode networking already
routes 10.0.2.2 to the host's loopback, so there is no tap device, no
bridge, no host-side `ip` command and no root on the host — the server
stays bound to 127.0.0.1. Verified end to end against an Alpine 3.21.3
`virt` guest (kernel 6.12.13-0-virt): mount with three options and no
`modprobe`, then ls/read/write/mkdir/dd/rename/unlink/umount.

Firecracker gets NFSv4.1, because it cannot do 9P at all: it emulates no
`virtio-9p` device, and its published guest kernels ship
`# CONFIG_NET_9P is not set`. Those kernels do carry NFSv4.1 (and not
NFSv3), which is exactly the half that landed in #7. The tap wiring that
Firecracker requires is not yet witnessed here, and the page says so.

Also records both in `.agents/environment.md`, and amends the NFS note in
AGENTS.md: NFSv4.1 was listed as having no real-client verification for
want of a `mount.nfs` on this host, and a VM guest supplies one — the
same server mounted `vers=4.1` from a real Linux kernel client and passed
the same workload. Still not a Tier-2 column; that would mean carrying a
VM in the suite.

Guide pages renumbered to slot it after Mounting; URLs are unaffected,
since the numeric prefix is routing-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A real Firecracker v1.16.1 microVM (guest kernel 6.1.102, their own
ubuntu-22.04 root image) mounted this NFSv4.1 server and passed the same
workload the QEMU columns do: ls, read, write, mkdir -p, 16 MiB dd both
directions, rename, unlink, umount.

Two findings worth the page:

- **No `nfs-common` is needed in the guest.** Firecracker's root image has
  neither `mount.nfs` nor `mount.nfs4`, and the mount works anyway: with
  `addr=` and `clientaddr=` given explicitly there is nothing left for the
  helper to resolve, so `mount(8)` falls through to the `mount(2)` syscall.
  That is the difference between "rebuild your image" and "nothing to
  install", so the page now spells both options out deliberately.
- **The guest needs no network commands**, given an `ip=` kernel boot arg.

The page drops its "not witnessed" note and gains the tap setup, a
complete `vm.json`, and the mount that was actually run.

`.agents/environment.md` records the reproduction, including the part
that made it possible without a container restart: this container has
`/dev/net/tun` but no `CAP_NET_ADMIN` in its bounding set, so `ip tuntap
add` is refused even for root — while a new user namespace grants a full
capability set over the netns it owns, so the tap, the server and the VM
all run inside one `unshare -Urn --map-root-user`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebasing onto main picked up `fix: keep every entry point loadable by
node's type stripping`, which removes the one constraint the VM harnesses
worked around: `node src/nfs/…` used to die on `src/nfs/v4/state.ts` with
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, so they imported `dist/` after a build.
Re-verified against the rebased tree and corrected the note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pi0
pi0 merged commit 559620f into main Jul 29, 2026
12 checks passed
@pi0
pi0 deleted the docs/vm-9p branch July 29, 2026 23:55

This branch was successfully deployed

1 active deployment
Preview — 02bf8f5f Deployed Jul 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants