docs/sandboxes/lifecycle.mdx
A sandbox has a simple lifecycle: create it, use it, stop it when it is idle, start it again when you need it, and remove it when you are finished. Stopping preserves the sandbox's configuration and filesystem, while removing deletes its sandbox-owned state.
Creating a sandbox starts the microVM and waits until it is ready to accept commands. Give each sandbox a name so you can find and manage it later. Names must be non-empty and no longer than 128 UTF-8 bytes.
<CodeGroup> ```typescript TypeScript // Local attached handle: sandbox stops when your process exits await using sb = await Sandbox.builder("worker").image("python").create();// Detached: sandbox survives after your process exits const detached = await Sandbox.builder("worker") .image("python") .detached(true) .create();
```rust Rust
let sb = Sandbox::builder("worker")
.image("python")
.create()
.await?;
sb = await Sandbox.create("worker", image="python")
sb, err := m.CreateSandbox(ctx, "worker", m.WithImage("python"))
sb = Microsandbox::Sandbox.create("worker", image: "python")
msb create python --name worker
An attached local SDK handle normally stops its sandbox when the client process exits. See Keep a sandbox running when the sandbox should outlive that process.
Stopping gracefully terminates guest processes and shuts down the VM. The sandbox moves to Stopped, but its configuration and filesystem remain available for the next start.
const sb = await Sandbox.start("worker");
```rust Rust
sb.stop().await?;
let sb = Sandbox::start("worker").await?;
await sb.stop()
sb = await Sandbox.start("worker")
_ = sb.Stop(ctx)
sb, err := m.StartSandbox(ctx, "worker")
sb.stop
sb = Microsandbox::Sandbox.start("worker")
msb stop worker
msb start worker
Use restart when you want to stop and start the sandbox in one operation. If it is already stopped or crashed, restart starts it directly. SDK receiver methods preserve the sandbox's identity and configuration while replacing only the running VM instance.
msb restart worker
On microsandbox cloud, restart and destroy can request a graceful stop, but timeout expiry cannot escalate to a force kill. Force controls are local-only, and detached-start controls affect only local process ownership.
Use connect_or_create when your application wants a sandbox with a stable name but does not know whether it already exists. This is useful for workers, development environments, and services that reconnect after the client process restarts.
The operation:
let sb = Sandbox::builder("worker")
.image("python")
.memory(1024)
.connect_or_create()
.await?;
sb = await Sandbox.connect_or_create(
"worker",
image="python",
memory=1024,
)
sb, err := m.ConnectOrCreateSandbox(ctx, "worker",
m.WithImage("python"),
m.WithMemory(1024),
)
sb = Microsandbox::Sandbox.connect_or_create(
"worker",
image: "python",
memory: 1024
)
If you already have a SandboxHandle, use connect_or_start to connect to that exact sandbox or start it when needed.
let handle = Sandbox::get("worker").await?;
let sb = handle.connect_or_start().await?;
handle = await Sandbox.get("worker")
sb = await handle.connect_or_start()
handle, err := m.GetSandbox(ctx, "worker")
sb, err := handle.ConnectOrStart(ctx)
handle = Microsandbox::Sandbox.get("worker")
sb = handle.connect_or_start
Detach a local sandbox when it should keep running after the client process exits. You can reconnect to it later by name.
<CodeGroup> ```typescript TypeScript const sb = await Sandbox.builder("worker") .image("python") .detached(true) .create(); ```let sb = Sandbox::builder("worker")
.image("python")
.detached(true)
.create()
.await?;
sb = await Sandbox.create("worker", image="python", detached=True)
await sb.detach()
sb, err := m.CreateSandbox(ctx, "worker",
m.WithImage("python"),
m.WithDetached(),
)
sb = Microsandbox::Sandbox.create("worker", image: "python", detached: true)
sb.detach
msb run -d python --name worker
You can also detach an existing SDK receiver with detach() (Detach in Go).
List sandboxes to discover what exists, or get one by name when you already know which sandbox you need.
<CodeGroup> ```typescript TypeScript const page = await Sandbox.list(); for (const handle of page.sandboxes) { console.log(`${handle.name}: ${handle.status}`); }const handle = await Sandbox.get("worker"); console.log(handle.status);
```rust Rust
for handle in Sandbox::list().await?.sandboxes {
println!("{}: {:?}", handle.name(), handle.status_snapshot());
}
for handle in (await Sandbox.list()).sandboxes:
print(f"{handle.name}: {handle.status}")
handle = await Sandbox.get("worker")
print(handle.status)
page, err := m.ListSandboxes(ctx)
for _, handle := range page.Sandboxes {
fmt.Printf("%s: %s\n", handle.Name(), handle.Status())
}
handle, err := m.GetSandbox(ctx, "worker")
fmt.Println(handle.Status())
msb ls
msb ps worker
Use wait_until_stopped when another part of your application is responsible for stopping the sandbox and you only need to wait for it to finish.
let result = sb.wait_until_stopped().await?;
result = await sb.wait_until_stopped()
result, err := sb.WaitUntilStopped(ctx)
result = sb.wait_until_stopped
Use wait_for_status (waitForStatus in TypeScript and WaitForStatus in Go) when you need to wait for a specific lifecycle state. It has no built-in timeout, so use the language's normal timeout or cancellation primitive around it.
Use msb modify, or the SDK modify() methods, to change an existing sandbox without recreating it. Some changes apply immediately, some affect future commands, and some take effect after a restart.
let plan = sb.modify()
.cpus(4)
.memory(4096)
.apply()
.await?;
plan = await sb.modify(cpus=4, memory=4096)
plan, err := sb.Modify(ctx, m.ModifyOptions{CPUs: 4, MemoryMiB: 4096})
msb modify api --cpus 4 --memory 4G
See Live Modify for the change model, CPU and memory resize, labels, environment variables, secrets, and storage sizing.
Use ping to check that a running sandbox's guest agent is reachable. A ping is only a health check and does not reset the idle timer. Use touch when you intentionally want to keep the sandbox active.
await sb.touch();
```rust Rust
let ping = sb.ping().await?;
println!("agent reachable in {:?}", ping.latency);
sb.touch().await?;
msb ping worker
msb touch worker
# Check health, then keep the sandbox active if it is reachable
msb ping worker --touch
Use a drain when existing commands should finish but new commands should be rejected. The sandbox moves to Draining, waits for in-flight commands, and then stops. This is useful when rotating worker sandboxes without interrupting active jobs.
sb.request_drain().await?;
await sb.request_drain()
err := sb.RequestDrain(ctx)
If a sandbox does not respond to a graceful stop, force-kill it. This ends the VM immediately without waiting for guest processes to shut down.
<CodeGroup> ```typescript TypeScript await sb.kill(); ```sb.kill().await?;
await sb.kill()
err := sb.Kill(ctx)
msb stop --force worker
Use destroy when you have an SDK receiver and want to stop and remove that exact sandbox in one operation. It requests a graceful stop by default, escalates after the configured timeout, and refuses to act on a replacement that reused the name.
sb.destroy().await?;
await sb.destroy()
err := sb.Destroy(ctx)
sb.destroy
Use remove when the sandbox is already stopped and you want to delete it by name.
Sandbox::remove("worker").await?;
await Sandbox.remove("worker")
err := m.RemoveSandbox(ctx, "worker")
Microsandbox::Sandbox.remove("worker")
msb rm worker
For a local sandbox, removal deletes sandbox-owned state while leaving independently managed resources intact:
| Removed | Kept |
|---|---|
| Sandbox record, configuration, status, labels, and run history | Cached OCI images and layers |
Managed OCI writable root disk (upper.ext4) and its guest filesystem changes | Named volumes and their contents |
| Captured sandbox logs | Snapshots created from the sandbox |
| Runtime staging files, including generated scripts | Bind-mounted host files or directories |
| Root filesystem pin metadata for this sandbox | User-supplied root disk images |
Removing a sandbox does not undo writes made to a named volume, bind mount, or user-supplied disk image. On cloud, removal deletes the remote sandbox resource; the local disk details above do not apply.
For production workloads, configure a maximum lifetime or idle timeout so sandboxes shut down automatically.
<CodeGroup> ```typescript TypeScript await using sb = await Sandbox.builder("worker") .image("python") .maxDuration(3600) // maximum sandbox lifetime in seconds .idleTimeout(300) // auto-drain after 5 minutes of inactivity .create(); ```let sb = Sandbox::builder("worker")
.image("python")
.max_duration(3600)
.idle_timeout(300)
.create()
.await?;
sb = await Sandbox.create(
"worker",
image="python",
max_duration=3600,
idle_timeout=300,
)
sb, err := m.CreateSandbox(ctx, "worker",
m.WithImage("python"),
m.WithMaxDuration(time.Hour),
m.WithIdleTimeout(5*time.Minute),
)
Most applications only need to distinguish between Running and Stopped. The complete set is useful for status displays and recovery logic.
| Status | Meaning |
|---|---|
| Created | Configuration has been saved, but the sandbox has not started yet. |
| Starting | The VM and guest agent are starting. Commands are not ready yet. |
| Running | The sandbox is ready for exec, shell, and filesystem operations. |
| Draining | Existing commands may finish, but new commands are rejected. The sandbox stops when the drain completes. |
| Stopped | The VM is off. Configuration and sandbox state are preserved for a later start. |
| Crashed | The VM exited unexpectedly and can be started again. |
Some backends can also report Paused. Resume is not currently exposed through the SDKs, so start and connect operations do not treat a paused sandbox as stopped.
A sandbox name finds the current sandbox saved under that name. A Sandbox or SandboxHandle also carries an opaque stable ID (ID() in Go) for one exact saved sandbox. Treat the ID as an opaque value and use it for logging, correlation, and equality checks.
If worker is removed and a different sandbox is later created with the same name, an old receiver will not act on the replacement. Lifecycle methods return SandboxReplaced or the language's typed equivalent when they can detect this situation; an ID-addressed cloud request may instead report that the old resource no longer exists.
Concurrent callers are safe to use with the named lifecycle APIs. create remains strict, so only one same-name creation succeeds. connect_or_create may reuse the sandbox created by another caller, and connect_or_start remains attached to the exact identity held by its handle. Calls that encounter Starting wait for the sandbox to become ready instead of launching a second runtime.
Use msb logs or the SDK logs() method to read captured output from running, stopped, or crashed sandboxes. For source semantics, boot errors, and diagnostic flows, see Logs.
For exact lifecycle APIs, see TypeScript, Rust, Python, or Go. For lifecycle commands and the REST surface, see Sandbox commands and the Cloud API.