Isolation Requirement¶
rightsize's two backends give different isolation guarantees, and the difference matters the
moment a container runs code you don't fully trust — a user-submitted script, a plugin, an
untrusted image pulled at test time. withRequireIsolation() lets a container demand the
stronger guarantee and fail fast instead of silently running under the weaker one.
Guarantees¶
| microsandbox | Docker (fallback) | |
|---|---|---|
| Kernel | Its own — each sandbox is a full microVM | Shared with the host |
| Isolation boundary | Hardware virtualization (KVM/WHP) | Linux namespaces/cgroups |
| A container escape reaches | Only that microVM | The host kernel |
capabilities.hardwareIsolated |
true |
false |
Docker's namespace/cgroup isolation is real and sufficient for the vast majority of testing — running trusted images you built yourself against trusted test data. It is not a hardware security boundary: a kernel exploit inside a Docker container can reach the host. microsandbox's microVMs give each sandbox its own kernel, the same class of guarantee cloud providers rely on for multi-tenant workloads.
When to require isolation¶
Reach for withRequireIsolation() when a container will run something you did not write or
fully vet — grading a user's submitted code, sandboxing a plugin, executing an image pulled
from an untrusted registry at test time. For ordinary module containers (Redis, Postgres, the
image you built from your own Dockerfile) it's unnecessary overhead: you already trust the
image.
API¶
val untrusted = GenericContainer("some/untrusted-image:latest")
.withRequireIsolation()
untrusted.start()
start() checks capabilities.hardwareIsolated on the active backend before any create,
network, or port work — if it's false, start() throws IsolationRequiredException and no
sandbox is ever created:
withRequireIsolation() requires a hardware-isolated backend, but the active backend is
'docker', which is not — set RIGHTSIZE_BACKEND=microsandbox to use the microsandbox backend, which
runs each sandbox in its own microVM
Force the microsandbox backend with RIGHTSIZE_BACKEND=microsandbox (see
Backends) wherever isolation-requiring containers run.
Untrusted-code guidance¶
withRequireIsolation() picks the right backend; it doesn't replace the rest of a defense-in-depth
setup around code you don't trust:
- Cap memory.
withMemoryLimit(megabytes)bounds what the sandbox can consume — untrusted code shouldn't be able to exhaust host memory even inside a microVM. - Block network egress.
withNetworkDisabled()(msb-only — see below) stops untrusted code from reaching the public internet at all, on top of hardware isolation. - No secrets in the environment.
withEnv(...)values are visible to whatever runs inside the container; never pass API keys, credentials, or other secrets into a container running untrusted code. - Prefer
withRequireIsolation()over trusting an auto-selected backend. Auto-selection picks microsandbox when available but silently falls back to Docker when it isn't (no KVM/WHP, unsupported host) — exactly the situationwithRequireIsolation()is for: fail the start instead of running untrusted code under the weaker guarantee without anyone noticing. - Mount files read-only.
withCopyFileToContainermounts are read-write by default, and the mount is a view of the host file, not a copy — a guest write reaches the host file itself. Unless the untrusted code genuinely needs to write back to the host filesystem, construct the mount withreadOnly = true, which both backends enforce as a guest-side write block.
Blocking network egress: withNetworkDisabled()¶
val untrusted = GenericContainer("some/untrusted-image:latest")
.withRequireIsolation()
.withNetworkDisabled()
untrusted.start()
What this does and doesn't cover:
- msb-only. On the microsandbox backend,
withNetworkDisabled()emits--net private. Published ports keep serving, and links to siblings on a privateNetworkkeep working — only outbound connections to the public internet fail. - Docker ignores it entirely.
withNetworkDisabled()on the Docker backend is a no-op: the container runs with normal networking, egress and all. There's no portable way to block egress on Docker while still keeping published ports reachable, so the flag is silently dropped rather than faked with a partial approximation. If blocking egress matters as much as isolation does, pairwithNetworkDisabled()withwithRequireIsolation()(as in the example above) so the run fails outright on Docker instead of quietly running with open egress. - Cannot be combined with
withNetwork(). A network-disabled container can't also join aNetworkto reach siblings by alias —start()throwsNetworkDisabledConflictExceptionbefore any backend call.