Back to Microsandbox

Live Modify

docs/sandboxes/tuning.mdx

0.6.1711.3 KB
Original Source

<Tooltip tip="modify and live resize are not yet available on microsandbox cloud; create a replacement sandbox with the new configuration."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

You do not need to replace a running sandbox just because its workload changes. modify can resize the VM, update host-side metadata, rotate existing secrets, and change the defaults used by future commands while the sandbox stays up.

<Frame> </Frame>

What can modify change?

The live paths come first, but modify also plans changes that need a restart or the next start:

ChangeWhen it takes effectNotes
CPU and memoryLive, within the boot-time max_* ceilingsThe VM and workload keep running
LabelsLiveHost-side metadata; guest processes do not change
Existing secret material or removalLiveAdding a secret or changing its placeholder needs a restart
Environment and workdirFuture execsProcesses that are already running keep their current values
max_cpus and max_memoryRestart or next startThese ceilings are fixed when the VM boots
Root disk sizeRestart or next startManaged and flat OCI disks grow only; tmpfs changes on boot
Named volumes, mount tmpfs, and user disk imagesOutside modifyCapacity is managed where the storage is defined

The default policy applies only changes that can complete without restarting. If a patch contains one restart-required change, microsandbox rejects the whole patch and the old configuration stays intact.

Modify a running sandbox

This patch doubles the running sandbox's CPU and memory and updates a label in the same operation:

<CodeGroup> ```bash CLI msb modify worker --cpus 4 --memory 2G --label tier=web ```
typescript
const plan = await sandbox.modify({
  cpus: 4,
  memory: 2048,
  labels: { tier: "web" },
});
rust
let plan = sb.modify()
    .cpus(4)
    .memory(2048)
    .label("tier", "web")
    .apply()
    .await?;
python
plan = await sb.modify(
    cpus=4,
    memory=2048,
    labels={"tier": "web"},
)
go
plan, err := sb.Modify(ctx, m.ModifyOptions{
    CPUs:      4,
    MemoryMiB: 2048,
    Labels:    map[string]string{"tier": "web"},
})
</CodeGroup>

The result is a modification plan showing what changed and whether each change was applied. CPU and memory can take a moment to converge inside the guest. The new host limits take effect immediately, and the workload continues running.

Reserve resize headroom

Live growth needs capacity reserved when the VM boots. Set max_cpus and max_memory above the starting allocation when you create a sandbox that may need to scale:

<CodeGroup> ```bash CLI msb create python --name worker \ --cpus 2 --memory 1G \ --max-cpus 8 --max-memory 4G ```
typescript
await using sandbox = await Sandbox.builder("worker")
  .image("python")
  .cpus(2)
  .memory(1024)
  .maxCpus(8)
  .maxMemory(4096)
  .create();
rust
let sb = Sandbox::builder("worker")
    .image("python")
    .cpus(2)
    .memory(1024)
    .max_cpus(8)
    .max_memory(4096)
    .create()
    .await?;
python
sb = await Sandbox.create(
    "worker",
    image="python",
    cpus=2,
    memory=1024,
    max_cpus=8,
    max_memory=4096,
)
go
sb, err := m.CreateSandbox(ctx, "worker",
    m.WithImage("python"),
    m.WithCPUs(2),
    m.WithMemory(1024),
    m.WithMaxCPUs(8),
    m.WithMaxMemory(4096),
)
</CodeGroup>

The sandbox starts with two vCPUs and 1 GiB of memory, but it can grow live to eight vCPUs and 4 GiB. The ceilings default to the starting size, so omitting them leaves no live growth headroom.

Reserving headroom is cheap: spare vCPUs stay parked, and microsandbox backs spare memory only when the guest uses it. You can lower CPU or memory live without pre-planning, then grow back up to the booted ceiling later.

Preview before applying

Use a dry run when a patch mixes settings or you are unsure whether a restart is needed:

<CodeGroup> ```bash CLI msb modify worker --max-memory 16G --dry-run ```
typescript
const plan = await sandbox.modify({
  maxMemory: 16_384,
  dryRun: true,
});
rust
let plan = sb.modify()
    .max_memory(16_384)
    .dry_run()
    .await?;
python
plan = await sb.modify(max_memory=16_384, dry_run=True)
go
plan, err := sb.Modify(ctx, m.ModifyOptions{
    MaxMemoryMiB: 16_384,
    DryRun:        true,
})
</CodeGroup>

The planner classifies every requested field before it changes anything:

DispositionMeaning
liveApplies without restarting the VM
next startIs saved and applies the next time the sandbox starts
requires restartCannot affect the running VM under the default policy
unsupportedIs invalid for this sandbox or backing type

Live paths in detail

CPU and memory

Raise or lower CPU and memory while the workload runs:

<CodeGroup> ```bash CLI msb modify worker --cpus 4 --memory 2G ```
typescript
const plan = await sandbox.modify({ cpus: 4, memory: 2048 });
rust
let plan = sb.modify()
    .cpus(4)
    .memory(2048)
    .apply()
    .await?;
python
plan = await sb.modify(cpus=4, memory=2048)
go
plan, err := sb.Modify(ctx, m.ModifyOptions{
    CPUs:      4,
    MemoryMiB: 2048,
})
</CodeGroup>

Use msb ps to see allocation as effective / max, and msb metrics to check real usage before resizing. The apply result reports applied, converging, guest-refused, or failed for each resource so automation can wait for the guest to settle.

Labels

Labels are host-side metadata, so adding, changing, or removing one is immediate:

<CodeGroup> ```bash CLI msb modify worker --label tier=web --label-rm stale ```
typescript
const plan = await sandbox.modify({
  labels: { tier: "web" },
  labelsRemove: ["stale"],
});
rust
let plan = sb.modify()
    .label("tier", "web")
    .remove_label("stale")
    .apply()
    .await?;
python
plan = await sb.modify(
    labels={"tier": "web"},
    labels_rm=["stale"],
)
go
plan, err := sb.Modify(ctx, m.ModifyOptions{
    Labels:       map[string]string{"tier": "web"},
    LabelsRemove: []string{"stale"},
})
</CodeGroup>

Running guest processes do not change. The new labels are available to listing, selection, metrics attribution, and other host-side workflows. See Labels for naming and cardinality guidance.

Environment and workdir

Environment and workdir updates require no restart, but they affect only commands started after the patch:

<CodeGroup> ```bash CLI msb modify worker --env MODE=prod --workdir /app msb exec worker -- printenv MODE ```
typescript
await sandbox.modify({
  env: { MODE: "prod" },
  workdir: "/app",
});

const output = await sandbox.exec("printenv", ["MODE"]);
rust
sb.modify()
    .env("MODE", "prod")
    .workdir("/app")
    .apply()
    .await?;

let output = sb.exec("printenv", ["MODE"]).await?;
python
await sb.modify(env={"MODE": "prod"}, workdir="/app")
output = await sb.exec("printenv", ["MODE"])
go
_, err := sb.Modify(ctx, m.ModifyOptions{
    Env:     map[string]string{"MODE": "prod"},
    Workdir: "/app",
})
out, err := sb.Exec(ctx, "printenv", []string{"MODE"})
</CodeGroup>

Processes that were already running keep their original environment and working directory.

Secrets

Rotating the value of an existing secret is live because substitution happens at the host network boundary. Guest code keeps using the same placeholder while microsandbox begins injecting the new value:

<CodeGroup> ```bash CLI msb modify worker --secret [email protected] ```
typescript
const plan = await sandbox.modify({
  secrets: {
    GITHUB_TOKEN: {
      env: "GITHUB_TOKEN",
      allowedHosts: ["api.github.com"],
    },
  },
});
rust
use microsandbox::sandbox::SecretSource;

let plan = sb.modify()
    .secret(|secret| secret
        .env("GITHUB_TOKEN")
        .source(SecretSource::Env { var: "GITHUB_TOKEN".into() })
        .allow_host("api.github.com"))
    .apply()
    .await?;
python
plan = await sb.modify(
    secrets={
        "GITHUB_TOKEN": {
            "env": "GITHUB_TOKEN",
            "allowed_hosts": ["api.github.com"],
        },
    },
)
go
plan, err := sb.Modify(ctx, m.ModifyOptions{
    Secrets: map[string]m.SecretModifySpec{
        "GITHUB_TOKEN": {
            Env:          "GITHUB_TOKEN",
            AllowedHosts: []string{"api.github.com"},
        },
    },
})
</CodeGroup>

Adding a new secret or changing its guest-visible placeholder requires a restart because existing processes cannot receive a new placeholder. Removing a secret does not require recreating the sandbox. See Secrets for sources, host allow lists, and storage behavior.

Changes that need a boot boundary

Some settings define VM capacity or disk layout and cannot change in place:

ChangeApply nowDefer safely
Raise max_cpus or max_memory--restart--next-start
Grow a managed or flat OCI root disk--restart--next-start
Resize a tmpfs root diskOn restart--next-start
Add a new secret or change its placeholder--restart--next-start
<CodeGroup> ```bash CLI msb modify worker --max-memory 16G --next-start msb modify worker --root-disk 8G --restart ```
typescript
await sandbox.modify({
  maxMemory: 16_384,
  policy: "next_start",
});

await sandbox.modify({
  rootDiskSize: 8192,
  policy: "restart",
});
rust
sb.modify()
    .max_memory(16_384)
    .next_start()
    .apply()
    .await?;

sb.modify()
    .root_disk_size(8192)
    .restart()
    .apply()
    .await?;
python
from microsandbox import ModificationPolicy

await sb.modify(
    max_memory=16_384,
    policy=ModificationPolicy.NEXT_START,
)

await sb.modify(
    root_disk_size=8192,
    policy=ModificationPolicy.RESTART,
)
go
_, err := sb.Modify(ctx, m.ModifyOptions{
    MaxMemoryMiB: 16_384,
    Policy:       m.ModificationPolicyNextStart,
})

_, err = sb.Modify(ctx, m.ModifyOptions{
    RootDiskSizeMiB: 8192,
    Policy:          m.ModificationPolicyRestart,
})
</CodeGroup>

--next-start saves the desired configuration without touching the running VM. --restart stops and starts the sandbox only when the patch needs it. The default policy does neither and rejects restart-required changes.

Root disk resizing remains conservative: managed and flat OCI disks grow only, tmpfs can grow or shrink at the next boot, and user-supplied disk images are never resized by microsandbox. Named volume and mount capacity is managed where that storage is defined. See Volumes and Images for the storage model.

Reference

For every CLI flag and result state, see msb modify. The SDK sandbox references expose the same planner and policies for TypeScript, Rust, Python, and Go.