Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Platform support

Ferry gives every agent a warm, private target dir everywhere it runs. What varies is whether an agent’s first edit can reuse rustc’s incremental state, which needs the Linux namespace sandbox (or the experimental remap-paths mode).

  • Crate-level reuse: crates whose sources match the base are not rebuilt, the workspace’s included. An edit rebuilds the edited crate and its dependents from scratch. Without a canonical path, the few crates that captured the base’s absolute paths (a test baking in env!("CARGO_MANIFEST_DIR"), users of a proc macro reading it) also rebuild once after spawn.
  • Incremental reuse: on top of that, rustc recompiles only the parts of an edited crate that changed. In the prototype on ripgrep (2 vCPU) the first edit after spawning took 1.75 s with incremental reuse and 5.3 s with crate-level reuse only.

Agents in the namespace sandbox get both crate-level and incremental reuse.

After an agent’s first build, its own incremental state exists and later edits are incremental either way.

Summary

PlatformFork backendsSandboxFirst edit after spawn
Linux with unprivileged user namespaces (Debian, Fedora, Arch, NixOS, RHEL without STIG hardening, …)reflink, overlay, copynamespace sandbox with canonical pathsincremental reuse
Ubuntu 24.04 and later, with Ferry’s AppArmor profile installedreflink, overlay, copynamespace sandboxincremental reuse
Ubuntu 24.04 and later, default policyreflink, copynonecrate-level reuse
Docker, default seccomp profilereflink, copynonecrate-level reuse
macOS (APFS)reflink (clonefile), copynonecrate-level reuse; experimental remap-paths mode
Windowsnot supported

Ferry does not build on Windows: compiling it there stops with a compile_error! saying so.

Linux

The full feature set: agents build in a private user and mount namespace, at canonical paths. Forks are copy-on-write clones where the store’s filesystem supports them; elsewhere the overlay backend forks in constant time. This needs:

  • unprivileged user namespaces, which are on by default on Debian 12 and 13, Fedora, RHEL 9 and 10, Arch and NixOS, but not on Ubuntu 24.04 and later (AppArmor) or in Docker’s default seccomp profile, and are disabled by the DISA STIG for RHEL (user.max_user_namespaces = 0);
  • for the overlay backend, Linux 5.11 or newer and a store filesystem that supports user extended attributes (ext4, btrfs, XFS; tmpfs from 6.6).

When namespaces are unavailable, sandbox = "auto" falls back to building at host paths, and ferry base warns about it. See Troubleshooting for every case.

Ubuntu 24.04 and later

Ubuntu restricts unprivileged user namespaces with AppArmor (kernel.apparmor_restrict_unprivileged_userns = 1). Creating one succeeds, but the process gets no capabilities inside it, so mapping its uid and mounting fail. Ferry detects this and either:

  • uses the namespace sandbox once ferry setup apparmor has installed ferry to a root-owned path with its AppArmor profile (one confirmed sudo call; CI runs Ferry’s sandbox tests on Ubuntu 24.04 this way), or
  • falls back to crate-level reuse.

Ubuntu on WSL2 runs Microsoft’s kernel, which has no such sysctl, and Linux Mint 22 ships the restriction switched off; ferry setup apparmor reads the sysctl and says when nothing is needed.

See AppArmor on Ubuntu. Ubuntu 23.10 ships the restriction switched off.

Containers

Docker’s default seccomp profile allows unshare and mount only with CAP_SYS_ADMIN, and on AppArmor hosts Docker’s default AppArmor profile denies mounts, so Ferry falls back to crate-level reuse inside Docker. Rootless Podman’s default profiles allow them. On Ubuntu 24.04 and later hosts, the host’s restriction applies to container processes too, and only the host can lift it. If you run Ferry in a container with namespaces allowed, put FERRY_HOME on a volume, because overlayfs cannot use the container’s overlay root filesystem as its upper layer. See Containers for the seccomp profile and a devcontainer example. These container facts come from the profiles’ sources and standalone probes; Ferry itself has not been tested in containers.

Filesystems

Where the store lives decides which fork backends work and what they cost.

Filesystemreflink backendoverlay upper layerauto picksNotes
btrfsyesyesreflinkoverlay copy-up is a reflink
XFSyes (reflink=1, the mkfs default since xfsprogs 5.1)expected to work, not testedreflinkoverlay copy-up is a reflink
OpenZFS2.2.0, and 2.3.0 or newer; off by default in 2.2.2 to 2.2.xnot testedreflink where block cloning is on
ext4noyesoverlayoverlay copy-up copies the whole file
tmpfsnoLinux 6.6 or neweroverlay (6.6+)
NFS, CIFS, FUSEnot recommendednot recommendedferry doctor warns: slow builds, unreliable mtimes

Without the namespace sandbox auto never picks overlay: it uses reflink where clones work and copy elsewhere.

ferry doctor probes reflinks and overlayfs on the store’s actual filesystem rather than trusting this table.

macOS

Agents fork the base’s target dir with one clonefile(2) call on APFS (the reflink backend) and build at their own paths. macOS has no per-process mount namespaces, so there is no canonical path: crates are reused except the few that captured the base’s absolute paths, which rebuild once after spawn, and the first edit of a crate loses its incremental state.

The experimental remap-paths mode is designed for this case. On Linux it reuses 12 of 13 library codegen units after a fork to a new path, against 0 of 13 without it. That it works the same on macOS is predicted from rustc’s source but has not been verified on a Mac.

Bases and forks live in ~/Library/Caches/ferry; Ferry excludes base target dirs and agent build state from Time Machine backups. Agent worktrees live in ~/Library/Application Support/ferry/worktrees, because macOS may purge caches.

What ferry doctor reports

ferry doctor runs these checks and ends with the plan for new agents.

CheckStatusWhat it means
gitok or failgit --version; fails if git is missing
repositoryok or failthe main checkout Ferry will use; fails outside a git repository
configok or infothe ferry.toml that was loaded, or that defaults are in use
toolchainok or failthe rustc and cargo the repository selects
clippyok or infowhether cargo clippy works, when the toolchain works and the warm commands include the generated clippy command; bases skip it when it does not
freshnessokchecksum freshness (content hashes) or mtime stamping of files proven identical to the base
storeok or warnthe store path, its filesystem and free space; warns on NFS, CIFS and FUSE, and on overlayfs (a container’s root filesystem)
reflinkok or infowhether copy-on-write clones work on the store’s filesystem
namespacesok or warn (Linux)whether an unprivileged user and mount namespace with a bind mount works; the fix names the cause it found (AppArmor, with ferry setup apparmor as the fix or the path of a different ferry binary a Ferry profile covers; a sysctl; or a container)
overlayfsok or warn (Linux)whether overlayfs mounts inside such a namespace, with upper and work dirs in the store
sandboxinfo (macOS)that macOS has no mount namespaces
remap-pathsinfowhen bases would remap paths (remap-paths on, no namespace sandbox, rustc 1.98 or newer): cargo clippy runs are not remapped

The plan line reads like:

  → agents fork with reflink (btrfs supports copy-on-write clones) and build in a namespace sandbox at canonical paths (incremental reuse on the first edit)

on ext4:

  → agents fork with overlay (overlayfs works in the namespace sandbox on ext4; no copy-on-write clones on ext4 (FICLONE: Operation not supported (os error 95))) and build in a namespace sandbox at canonical paths (incremental reuse on the first edit)

or, without namespaces:

  → agents fork with reflink (btrfs supports copy-on-write clones) and build at their own paths (only crates that captured the base's paths rebuild; no incremental reuse on the first edit)

ferry --json doctor adds the raw probe results and the sysctls that explain namespace failures: user.max_user_namespaces, kernel.unprivileged_userns_clone, kernel.apparmor_restrict_unprivileged_userns, kernel.apparmor_restrict_unprivileged_unconfined and kernel.yama.ptrace_scope, where they exist, and probe_label, the namespace probe’s AppArmor label (ferry (enforce) when Ferry’s profile attached).