Backends¶
rightsize ships two implementations of a single SandboxBackend interface:
backend-microsandbox (microVMs, via the msb CLI) and backend-docker
(conventional containers, via docker-java). Both satisfy one behavioral contract,
verified by a shared test suite that runs against each — code you write targets
GenericContainer and never the backend directly, so the same test runs unchanged on
either. That same contract also holds across languages — see Cross-Language
Parity.
Backend selection¶
Selection is lazy and happens once per JVM, in this order:
RIGHTSIZE_BACKEND=microsandbox|docker, if set, wins outright — and it must be usable, or the run fails immediately naming the exact precondition that wasn't met (rather than silently falling through to the other backend).- Otherwise, microsandbox if the platform supports it: macOS on Apple Silicon, Linux
with a readable
/dev/kvm, or Windows (x86_64/arm64) — attempted directly, since msb's ownmsb doctorreports whether Windows Hypervisor Platform is actually usable, more reliably than a pre-boot probe could. - Otherwise, Docker if a daemon socket is reachable.
- Otherwise, fail — naming the exact precondition that failed for every backend that was considered, not just the first one.
Internally this is priority-based (microsandbox = priority 20, Docker = priority 10; the higher-priority supported backend wins when no explicit override is given), but you don't need to think about it in those terms day to day — the four-step list above is the whole story that matters from the outside.
Environment variables¶
| Variable | Effect |
|---|---|
RIGHTSIZE_BACKEND |
Force microsandbox or docker, overriding auto-selection. |
MSB_PATH |
Use a pre-installed msb binary; skips the download/provisioning step entirely. |
RIGHTSIZE_CACHE_DIR |
Relocate the runtime cache (default ~/.cache/rightsize; %LOCALAPPDATA%\rightsize on Windows, falling back to %USERPROFILE%\AppData\Local\rightsize if LOCALAPPDATA is unset). |
RIGHTSIZE_MSB_SKIP_DOWNLOAD |
true = fail with guidance instead of downloading — for air-gapped CI; pair with MSB_PATH or a pre-seeded cache. |
RIGHTSIZE_REAPER |
on (default) / sweep / off — controls orphan reaping (a crashed process's leftover sandboxes). See Orphan Reaping. |
RIGHTSIZE_REUSE |
Exact string true or 1 — the environment half of container reuse's double opt-in (a withReuse() container still needs this to actually reuse). See Container Reuse. |
DOCKER_HOST |
Standard docker-java variable. The Docker backend also honors the active docker CLI context (~/.docker/config.json) — set this if your daemon isn't at the default /var/run/docker.sock (Docker Desktop / Colima / OrbStack on a non-default socket, for instance). On Windows this is not optional: the Docker backend's zerodep transport speaks only unix sockets, so RIGHTSIZE_BACKEND=docker on native Windows needs a unix-socket-reachable daemon (e.g. the Docker Engine running inside WSL, with DOCKER_HOST pointed at its socket) — a Windows named pipe alone will not work. |
backend-microsandbox deep-dive¶
Provisioning¶
On first use, if no runtime is already cached (or MSB_PATH isn't set), rightsize
downloads a pinned msb release (currently 0.6.6) plus its libkrunfw companion
library from GitHub releases, matched to your OS/architecture
(msb-darwin-aarch64, msb-linux-x86_64, msb-linux-aarch64,
msb-windows-x86_64.exe, msb-windows-aarch64.exe, and the corresponding
libkrunfw-* asset).
Every downloaded asset is verified against the release's checksums.sha256 before
anything trusts it. Installation is atomic and crash-safe: both files download to temp
locations first, then libkrunfw moves into place, and the msb binary moves into
place last — so the binary's mere existence is the "install complete" marker. A
process crashing mid-install can never leave a state where a later run wrongly
believes an incomplete install is usable. A cross-process file lock
(FileChannel.lock()) serializes concurrent installs so parallel Gradle test workers
provision exactly once rather than racing each other — this holds on Windows too, where
the same kernel-held lock is released on process death exactly as it is on POSIX.
libkrunfw lives in a lib/ directory as a sibling of bin/msb (bin\msb.exe on
Windows) — msb's own resolver expects it at ../lib relative to the binary, and the
installer lays files out to match. The installed binary name is platform-derived:
suffixless msb on macOS/Linux, msb.exe on Windows, since there is no execute bit on
Windows to gate on — the install-validity check there is a plain exists-and-is-a-file
test instead of the POSIX executable-bit check.
Attached-mode supervision¶
microsandbox's detached mode (msb run -d) does not start the image's own
ENTRYPOINT — the VM boots with only its init process and the workload inside never
launches. rightsize's msb backend therefore runs every sandbox attached: each
container is a held child Process supervising its microVM, and the image's
ENTRYPOINT/CMD runs exactly as it would under Docker. Readiness for backend purposes
is "the sandbox name shows Running in msb ls" — not the attached process's own
exit code or stdout; workload logs come from msb logs, a separate channel. See
How It Works for more on why this shape was necessary.
On Windows, the attached msb run process's own stdout does not relay the guest
workload's output at all (confirmed empirically) — msb logs is not just the
backend's preferred channel there, it is the only one. Process teardown also differs
at the JVM level: the JVM has no POSIX signals on Windows, so Process.destroy() and
destroyForcibly() both call TerminateProcess — there is no separate graceful
variant to escalate from. This is harmless here because the actual graceful shutdown
is msb stop/msb rm, issued before the attached child is killed; killing the
process afterward is a pure cleanup step on every platform.
The provisioning cache¶
Everything lands under ~/.cache/rightsize/ on macOS/Linux, %LOCALAPPDATA%\rightsize
on Windows (or RIGHTSIZE_CACHE_DIR on any platform, if you've relocated it) — the
pinned msb toolchain, versioned by directory so a future rightsize release pinning a
newer msb doesn't collide with an older cached one. %LOCALAPPDATA% is used rather than
%USERPROFILE% because the cache holds a machine-local native toolchain, not
roaming-profile data.
backend-docker deep-dive: the zerodep story¶
The Docker backend talks to the daemon through docker-java, and specifically through
the docker-java-transport-zerodep transport — not httpclient5, despite that being
the more commonly reached-for docker-java transport. This wasn't a style preference;
it's a fix for a real, reproducible failure.
The problem: docker-java-transport-httpclient5 depends on Apache HttpClient 5.
If the consuming application's own classpath also manages httpclient5 to >= 5.4
(true of any Spring Boot 3.4+ application, for instance — exactly the shape of the
mirage-weather-service suite this library was built against), docker-java's
transport silently dials TCP localhost:2375 instead of the daemon's actual
unix:// socket, and reports the daemon as unreachable. This is a genuinely
surprising failure mode: your test suite worked yesterday, you bumped an unrelated
dependency today, and now every container test fails with a connection error that
has nothing obviously to do with Docker.
The fix: backend-docker uses docker-java-transport-zerodep
(ZerodepDockerHttpClient), which bundles its own JNA-based unix-socket client and
pulls in no httpclient5 at all. There is nothing you need to do about this — it's
handled entirely inside backend-docker — but if you're debugging a "Docker
unreachable" error that seems to have started after a dependency bump, and you're
using rightsize's Docker backend, this class of bug is now categorically
ruled out; look elsewhere.
Backend differences¶
The two backends are contract-equivalent — the same shared test suite passes against both — but a handful of edges are genuinely backend-specific rather than incidental timing quirks. Know these before you hit them:
- Read-only mounts aren't enforced in-guest on microsandbox 0.6.2.
FileMount.readOnly(underwithCopyFileToContainer) is honored by the Docker backend — the bind mount is genuinely read-only inside the container. On microsandbox, the guest currently gets a writable mount regardless of the flag. Don't rely on guest-side write protection underRIGHTSIZE_BACKEND=microsandbox. See Files & Memory. followOutput's tail-flush on microsandbox is a watchdog, not a stream close.msb logs -fdoesn't exit when its sandbox stops (a documented gap in msb 0.6.2), so the microsandbox backend polls in the background and replays only the not-yet-delivered tail once the sandbox is confirmed stopped. Consumers see the same ordered, no-duplicate output either backend produces — this is purely an implementation detail, not a behavior difference from the caller's point of view — but it does mean afollowOutputsubscriber on microsandbox can see its last line arrive slightly after the sandbox itself reports stopped, rather than exactly at stream EOF the way a Docker log stream closes. On Windows hosts the live stream is a poll too, not just the tail-flush:msb logs -fthere never relays new lines to its stdout pipe while the sandbox runs, so the backend polls non-followmsb logsfor the whole stream — same ordered, no-duplicate delivery, with per-line latency up to the poll interval (300 ms).- Network-alias tunnels on microsandbox serve one connection at a time. See Networking — this is a real capability gap versus Docker's native bridge networking, not just a timing quirk, and it means sustained bidirectional sibling traffic (a cross-container Kafka consumer, say) isn't something the microsandbox backend supports today.
- Readiness-probe caveats apply to both backends, not just microsandbox — see
Wait Strategies for why
Wait.forListeningPort()can be satisfied before the in-guest process is actually ready on either backend, and when to preferWait.forHttp/Wait.forLogMessageinstead. - Checkpoint/restore is supported on both backends, via different mechanisms.
capabilities.checkpointistrueon both — Docker commits the running container to a new image; microsandbox stops the sandbox, snapshots its disk, and reboots it, which restarts the workload (capabilities.checkpointRestartsWorkload). See Checkpoint / Restore for the full comparison. - Native Windows msb support is upstream beta (microsandbox 0.6.6). The guest is
Linux on every host, so guest-side behavior (entrypoints,
/etc/hostsaliasing, the exec-stream tunnels) is unaffected by running on Windows — the Windows-specific surface is entirely host-side (downloading a.exe/.dllpair, the cache root, process supervision).RIGHTSIZE_BACKEND=microsandboxon a Windows host without a usable Windows Hypervisor Platform fails immediately naming that precondition, exactly as it does today on an Intel Mac or a KVM-less Linux box, rather than silently falling back to Docker. - The Docker backend on Windows needs a unix-socket-reachable daemon. The
zerodeptransport (see below) speaks unix sockets only, so a Docker daemon reachable purely via a Windows named pipe won't work — pointDOCKER_HOSTat a unix-socket endpoint (for example, the Docker Engine running inside WSL).