Files & Memory¶
MountableFile¶
MountableFile describes a file to mount into a container via
GenericContainer.withCopyFileToContainer(file, guestPath). This is a start-time mount,
configured before start() — see Copying Files for the runtime equivalent
(copyFileToContainer/copyContentToContainer/copyFileFromContainer), which acts on an
already-running container in either direction. Build a MountableFile from either a host path or
a classpath resource:
val fromDisk = MountableFile.forHostPath("/absolute/or/relative/path/on/disk")
val fromClasspath = MountableFile.forClasspathResource("fixtures/seed-data.sql")
container.withCopyFileToContainer(fromClasspath, "/docker-entrypoint-initdb.d/seed.sql")
forHostPathresolves the given path to an absolute path immediately; it doesn't check the file exists until mount time.forClasspathResourceresolves the resource against the current thread's context classloader, copies it into a fresh temp file (backends need a real host path to mount, not a classpath URL), and registers that temp file withFile.deleteOnExit(). That's fine for the short-lived test/build JVMs this is designed for — if your process is long-running and calls this repeatedly, be aware the temp files accumulate until the JVM actually exits.
Read-only mounts: a real backend difference¶
FileMount.readOnly (the flag underneath withCopyFileToContainer) is honored
faithfully by the Docker backend — the bind mount is genuinely read-only inside
the container. On the microsandbox backend, this is not currently enforced
in-guest (a known gap in msb 0.6.2): the guest gets a writable mount regardless of the
flag.
Practically: don't write a test that depends on a write to a "read-only" mount failing
when it's exercised under RIGHTSIZE_BACKEND=microsandbox. If your test's correctness
depends on genuine write protection, verify it against the Docker backend, or add an
explicit in-test assertion that doesn't rely on the guest OS enforcing it.
withMemoryLimit(megabytes)¶
Caps a container's guest memory. Maps to msb's -m <megabytes>M flag on the
microsandbox backend and to HostConfig.memory (in bytes) on the Docker backend.
Leave it unset (the default) and each backend applies its own default sizing instead.
When you actually need this¶
microsandbox's default microVM has a small amount of guest-available RAM — roughly 450 MB in practice (the exact figure observed varies a little run to run, somewhere in the ~443–454 MB range, but "~450 MB" is the number to plan around). Most images fit comfortably under that. Two categories don't, and both are shipped modules that had to raise the limit to boot at all:
Spring Cloud Config Server. The hyness/spring-cloud-config-server image is built
on a Paketo buildpack, and Paketo's memory calculator sizes the JVM's fixed regions
(heap + metaspace + thread stacks, computed ahead of time from the image's own
metadata) to around 688 MB — comfortably over the microVM default. SpringCloudConfigContainer
sets withMemoryLimit(1024) for exactly this reason; without it, the JVM fails to
launch under the microsandbox backend (it boots fine under Docker, whose containers
aren't memory-constrained by default, which is precisely why this class of bug is easy
to miss if you only ever test on Docker).
Apache Pinot's QuickStart cluster. PinotContainer runs a QuickStart -type EMPTY
process tree — ZooKeeper, controller, broker, server, and minion, four JVMs plus
ZooKeeper, all inside one container — and the image itself bakes in
JAVA_OPTS=-Xms4G -Xmx4G for the QuickStart driver JVM alone, before any of the four
sub-JVMs it spawns have taken anything. The original plan was withMemoryLimit(2048)
by analogy with the Config Server fix; that under-shot badly. Measured directly against
real boots:
| Memory limit | Result |
|---|---|
| 2048 MB | OOM-killed — timed out waiting for /health, reaped by the kernel |
| 2560 MB | OOM-killed |
| 3072 MB | Boots; /health returns 200 within ~15s — but docker stats settles at ~99% of the limit, and under that pressure the controller's Helix-backed schema/table-config RPCs intermittently time out even though /health reports 200 |
| 4096 MB | Boots cleanly; docker stats settles at ~73–75%; schema POST succeeded on every attempt across a 60s repeated-POST probe |
PinotContainer ships with withMemoryLimit(4096) — the lowest round number that
leaves real headroom above the image's own 4 GiB heap request, not merely enough to
dodge the OOM killer outright. Verified stable on both backends. If you're
configuring memory limits for your own JVM-heavy image, treat "boots without getting
OOM-killed" as a necessary but not sufficient bar — leave headroom, the way this
module does, or you'll see the same kind of intermittent RPC timeout under memory
pressure that 3072 MB produced here.
Images that don't need it¶
Most modules never touch withMemoryLimit at all — Redis, an Erlang VM like
RabbitMQ, single-process HTTP servers like WireMock or ClickHouse's server (not a JVM
process at all), and even MySQL/MariaDB's InnoDB default footprint all boot cleanly
under the ~450 MB default with no adjustment. Only reach for withMemoryLimit when
you actually observe a boot failure or OOM under the microsandbox backend — don't
apply it prophylactically to every module.