Skip to main content
A plain container never goes quiet on its own: its main process runs until something stops it. A fleet where every sandbox reports running forever is one where nothing can ever be safely reclaimed — and the disk fills with workspaces nobody can account for. Two deadlines fix that, and both are on by default. Each is enforced twice: by the sandbox itself, and by a sweep in your process. Neither deletes anything. A stopped sandbox restarts on the next create() with everything the last command installed still there; a retired one gets a fresh container over the same warm workspace. Only remove() deletes.

Per instance, or per sandbox

Set the default for a fleet on the constructor, and override it for one sandbox that needs different treatment:
0 disables a deadline, per sandbox or per instance.

The deadlines live on the container

They are recorded as labels rather than held in the process. That means the sweep honours them after a restart, and any other process reading the fleet sees the same clocks — a sandbox created by a worker that has since exited is still retired on time, by whoever is sweeping.

A command in flight is never interrupted

Neither deadline touches a sandbox with a command running. The maximum lifetime is a ceiling on how long a sandbox can be reused, not a kill switch under a running build. A three-hour build in a sandbox with a one-hour ceiling finishes; it is retired on the next sweep afterwards.

The sandbox stops itself

Every sandbox’s main process is a small watchdog (sh, date, sleep and /proc, so it runs in busybox and glibc images alike). Every few seconds it looks for any other process in the sandbox:
  • Something is running — a command, or anything a command left in the background — and the idle clock resets.
  • Nothing has run for idleTimeoutMs, and it exits. The container stops.
  • At maxLifetimeMs plus idleTimeoutMs it exits whatever is running: a backstop behind the sweep’s gentler retirement below, which lets a command in flight finish.
This is what makes the deadlines hold when no process of yours is alive: a sandbox created by a test, a CLI, or a server that has since restarted stops on time anyway. It is what a microVM runtime does natively. The watchdog writes nothing, so it works under a read-only root, and it traps SIGTERM, so stop() returns at once rather than waiting out its grace. selfStop: false gives a sandbox sleep infinity instead, and leaves stopping it entirely to the sweep. list() tells the two apart through the nativesandbox.selfStop label.

Sweeping

A timer runs both passes automatically, at a fraction of the soonest deadline so a sandbox is stopped within — not up to double — the interval asked for. It is unref’d, so it will never keep your process alive on its own. housekeeping: false does not start it — for a short-lived CLI, or a process that should never stop sandboxes it did not create. Sandboxes still stop themselves. Run them yourself when you want control:
Both accept a now for testing:
Call sandboxes.close() at shutdown to stop the timer.

A sandbox this process has never seen

One left running by a previous process is counted as used from the moment it is first seen, so it is stopped one full timeout later rather than immediately and rather than never. A restart does not mass-stop a working fleet, and it does not forget about it either.

Choosing the numbers

The cost of stopping too eagerly is a cold npm install on the next command — exactly what the reuse rule exists to avoid. The cost of stopping too late is a container holding memory it is not using. Five minutes spans a burst of activity from one user without keeping an abandoned sandbox “running” by the time anyone looks. Raise it if your work arrives in widely spaced bursts; raising it is the safe direction, because a stopped sandbox keeps its cache anyway.