---
title: Sandbox commands
sidebarTitle: "Sandbox"
description: Create, run, and interact with sandboxes from the CLI
icon: "cube"
---

## msb run

Create a sandbox and optionally run a command. Without `--name`, the sandbox is ephemeral and removed when the command finishes. With `--name`, it persists for later use.

```bash
# Ephemeral: runs and cleans up
msb run python -- python -c "print('hello')"

# Named: persists after exit
msb run --name devbox ubuntu -- bash

# With volumes, ports, and environment
msb run --name api \
  -v ./src:/app \
  -v pydata:/data \
  -p 8000:8000 \
  -e DEBUG=true \
  -w /app \
  python

# Detached (boots in background; run work later with exec)
msb run -d --name worker python
msb exec worker -- python worker.py

# Publish on all IPv4 host interfaces instead of the default 127.0.0.1
msb run --name public-api -p 0.0.0.0:8000:8000 python
```

| Flag | Description |
|------|-------------|
| `-n`, `--name` | Sandbox name, up to 128 UTF-8 bytes (if omitted, sandbox is ephemeral) |
| `-c`, `--cpus` | Number of virtual CPUs to allocate |
| `-m`, `--memory` | Amount of memory (e.g. `512M`, `1G`) |
| `--oci-upper-size` | Writable overlay upper size for OCI images (e.g. `4G`, `8192M`) |
| `-v`, `--volume` | Mount a host path or named volume (`SOURCE:DEST[:OPTIONS]`, e.g. `./src:/app/src:ro,noexec`) |
| `-p`, `--port` | Forward a host port to the sandbox (`HOST:GUEST`, `BIND_ADDR:HOST:GUEST`, and `/udp` variants) |
| `-e`, `--env` | Set an environment variable (`KEY=VALUE`) |
| `-w`, `--workdir` | Working directory inside the sandbox |
| `--shell` | Default shell for interactive sessions |
| `-t`, `--tty` | Allocate a pseudo-terminal (enables colors, line editing) |
| `-d`, `--detach` | Boot in the background and print the sandbox name. Commands after `--` are not run in detached mode; use `msb exec` afterward |
| `--timeout` | Kill the command after this duration (e.g. `30s`, `5m`, `1h`). Per-command; the sandbox stays alive |
| `--rlimit` | Set a POSIX resource limit (e.g. `nofile=1024`, `nproc=64`, `as=1073741824`) |
| `--detach-keys` | Key sequence to detach from interactive session (default: `ctrl-]`) |
| `--replace` | Replace an existing sandbox with the same name |
| `--replace-with-timeout` | How long to wait after `SIGTERM` before `SIGKILL` during a replace (`0`, `500ms`, `5s`, `2m`). Implies `--replace`. Default `10s` when `--replace` is set alone |
| `-q`, `--quiet` | Suppress progress output |
| `--entrypoint` | Override the image's default entrypoint command |
| `--init` | Hand off PID 1 to this init binary (absolute path) inside the guest after agentd's setup. See [Custom init system](/sandboxes/customize#custom-init-system) |
| `--init-arg` | Argv entry appended to the handoff init. Repeatable. Defaults to `[<--init>]` when empty |
| `--init-env` | Env var passed to the handoff init only (`KEY=VALUE`). Repeatable; merged on top of the inherited env |
| `-H`, `--hostname` | Set the guest hostname (defaults to sandbox name) |
| `-u`, `--user` | Run commands as the specified user (e.g. `nobody`, `1000`, `1000:1000`) |
| `--pull` | When to pull the image: `always`, `if-missing` (default), `never` |
| `--log-level` | Log verbosity for the sandbox runtime (`error`, `warn`, `info`, `debug`, `trace`) |
| `--tmpfs` | Mount a temporary in-memory filesystem (`PATH`, `PATH:SIZE`, or `PATH:SIZE:OPTIONS`) |
| `--script` | Register a shell snippet as a named script (`NAME=BODY`). Decodes `\n`, `\t`, `\r`, `\\`, `\"`, `\'`; unknown escapes pass through. Wrapped with a shebang from `--shell` (default `/bin/sh`). Available at `/.msb/scripts/<name>` and on `PATH` |
| `--script-raw` | Register exact inline script contents (`NAME=BODY`). No escape decoding or shebang is added; caller must include `#!` if direct execution is needed |
| `--script-path` | Register a script from a host file (`NAME:PATH`). File contents are read verbatim |
| `--copy` | Copy a host file or directory into the guest rootfs before boot (`SRC:DST`) |
| `--copy-file` | Copy a host file into the guest rootfs before boot (`SRC:DST`) |
| `--copy-dir` | Copy a host directory into the guest rootfs before boot (`SRC:DST`) |
| `--mkdir` | Create a directory in the guest rootfs before boot |
| `--rm` | Hide/remove a path from the guest rootfs before boot |
| `--max-duration` | Kill the entire sandbox after this duration (e.g. `30s`, `5m`, `1h`). Sandbox-level lifetime limit |
| `--idle-timeout` | Stop the sandbox after this period of inactivity (e.g. `30s`, `5m`, `1h`) |
| `--no-net` | Disable all network access by default. Sugar for `--net-default deny`. Combine with `--net-rule allow@<target>` entries to build an allowlist |
| `--net-rule` | Network rule. Repeatable; each value is a comma-separated list of rule tokens (see [Network rule syntax](#network-rule-syntax) below). Adds entries to the policy in argv order |
| `--net-default` | Default action (`allow` or `deny`) for traffic in both directions that doesn't match any `--net-rule`. Mutually exclusive with `--net-default-egress` / `--net-default-ingress` |
| `--net-default-egress` | Default action for unmatched egress traffic. Mutually exclusive with `--net-default` |
| `--net-default-ingress` | Default action for unmatched ingress traffic. Mutually exclusive with `--net-default` |
| `--no-dns-rebind-protection` | Allow DNS responses pointing to private/internal IP addresses |
| `--dns-nameserver` | Nameserver to forward DNS queries to (repeatable; `IP` or `IP:PORT`). Overrides the host's `/etc/resolv.conf` |
| `--dns-query-timeout-ms` | Per-DNS-query timeout in milliseconds (default: 5000) |
| `--net-ipv4-pool` | IPv4 pool used for per-sandbox `/30` guest subnets (default: `172.16.0.0/12`) |
| `--net-ipv6-pool` | IPv6 pool used for per-sandbox `/64` guest prefixes (default: `fd42:6d73:62::/48`) |
| `--max-connections` | Limit the number of concurrent network connections |
| `--trust-host-cas` | Ship the host's trusted root CAs into the guest so outbound TLS works behind corporate MITM proxies (Cloudflare Warp Zero Trust, Zscaler, etc.) whose gateway CA is installed on the host but unknown to the guest's stock bundle. Opt-in; by default the guest validates against its stock Mozilla bundle only |
| `--secret` | Inject a secret that is only sent to an allowed host (`ENV@HOST` or `ENV=VALUE@HOST`) |
| `--on-secret-violation` | Action when a secret is sent to a disallowed host (`block`, `block-and-log`, `block-and-terminate`, `passthrough`) |
| `--tls-intercept` | Intercept and inspect HTTPS traffic via a built-in TLS proxy |
| `--tls-intercept-port` | TCP port to apply TLS interception on (default: 443) |
| `--tls-bypass` | Skip TLS interception for a domain (e.g. `*.internal.com`) |
| `--no-block-quic` | Allow QUIC/HTTP3 traffic (blocked by default when TLS interception is on) |
| `--tls-intercept-ca-cert` | Use a custom CA certificate for TLS interception (PEM file) |
| `--tls-intercept-ca-key` | Use a custom CA private key for TLS interception (PEM file) |
| `--tls-upstream-ca-cert` | Trust an additional CA certificate for upstream server verification (PEM file). Can be specified multiple times |

Secret specs support two forms:

| Form | Example | Notes |
| ---- | ------- | ----- |
| `ENV@HOST` | `OPENAI_API_KEY@api.openai.com` | Recommended. Reads the real value from the same-named host environment variable, so the value is not placed in the command line |
| `ENV=VALUE@HOST` | `API_KEY=$OTHER_API_KEY@api.openai.com` | Explicit value form. Use for custom mappings or literal values. Shell-expanded values are passed to `msb` as command arguments |

Because shells expand `$VAR` before launching `msb`, prefer `ENV@HOST` when the guest env var and host env var have the same name.

Volume options use Docker-style colon syntax: `SOURCE:DEST:ro,noexec`. Supported `-v` flags are `ro`, `rw`, `nosuid`, and `noexec`; `stat-virt=` and `host-perms=` remain key-value options for bind and named mounts. `nosuid` is always enforced for guest volume mounts, so passing it is only an explicit assertion. `ro` and `noexec` cannot be undone by user commands with a guest remount. `noexec` blocks direct execution from the mount, but interpreters can still read scripts from it, such as `sh /mnt/script.sh`.

Tmpfs options use the same option block: `--tmpfs /tmp:1G:noexec` or `--tmpfs /seed:size=64,ro,noexec`.

Rootfs patch flags are creation-time mutations. They apply before the sandbox starts and become part of the sandbox's initial writable layer:

```bash
msb run alpine \
  --copy ./config.toml:/etc/app/config.toml \
  --copy-dir ./certs:/etc/app/certs \
  --mkdir /var/cache/app \
  --rm /etc/motd
```

Use `msb copy` for live file transfer after a sandbox exists.

When no `--` command is given, the image's `entrypoint` and `cmd` are used as the default process. If the image has neither, an interactive shell is started. When a command is given via `--`, it replaces the image `cmd` but the `entrypoint` is preserved.

### Network rule syntax

`--net-rule` takes one or more comma-separated rule tokens. The token grammar is:

```
<action>[:<direction>]@<target>[:<proto>[:<ports>]]
```

| Field | Values |
| ----- | ------ |
| `action` | `allow`, `deny` |
| `direction` | `egress` (default), `ingress`, `any` |
| `target` | See *Targets* below |
| `proto` | `tcp`, `udp`, `icmpv4`, `icmpv6`, `any` (default) |
| `ports` | `<port>`, `<lo>-<hi>`, or `any` (default) |

**Targets**

| Form | Example | Notes |
| ---- | ------- | ----- |
| IP / CIDR | `198.51.100.5`, `10.0.0.0/8`, `[2001:db8::]/32` | IPv6 must be bracketed |
| Domain (exact) | `example.com` | Use `domain=public` to disambiguate from the `public` group |
| Domain suffix | `*.example.com`, `suffix=example.com` | Matches the apex and any subdomain at any depth. Suffixes must be at least two labels: `*.com` and `suffix=local` are rejected to prevent accidental blast radius |
| Group | `public`, `private`, `multicast`, `loopback`, `link_local`, `metadata`, `any` | Pre-defined IP groups |

Wildcard quoting: the rule grammar uses `@`, `:`, and `,` which are shell-significant, so always quote `--net-rule` values. The `*.` in suffix shorthand mirrors the syntax already used by `--tls-bypass` and `--secret`.

**Common compositions**

```bash
# Allowlist: deny by default, allow specific destinations
msb run alpine --net-default deny --net-rule "allow@github.com,allow@*.githubusercontent.com"

# Blocklist: allow by default, deny specific destinations
msb run alpine --net-default allow --net-rule "deny@*.tracking.com,deny@*.ads.example"

# Airgapped: equivalent to `--net-default deny`
msb run alpine --no-net

# Airgapped except one host (allowlist with --no-net sugar)
msb run alpine --no-net --net-rule "allow@api.internal.corp"
```

## msb create

Create and boot a sandbox without running a command. Takes the same flags as `msb run` (except `--detach`).

```bash
msb create python --name worker -c 2 -m 1G
msb create --replace python --name worker            # Replace existing
msb create --replace-with-timeout 30s python --name worker  # Give the old one 30s to exit
msb create --replace-with-timeout 0 python --name worker    # SIGKILL immediately
```

## msb start

Resume a stopped sandbox.

```bash
msb start devbox
```

| Flag | Description |
|------|-------------|
| `-q`, `--quiet` | Suppress progress output |

## msb stop

```bash
msb stop devbox                # Graceful shutdown (10s grace)
msb stop --force devbox        # Force kill immediately
msb stop -t 30 devbox          # 30s grace before force kill
```

Graceful shutdown gives the sandbox a chance to finish writing any
pending data to disk before it exits, so files written inside the
sandbox aren't lost across a later `msb start`. If the sandbox is
still running after the timeout, it is force-killed.

`--force` and a timeout that elapses both end in a force-kill. Pending
writes that the workload hasn't `fsync`'d at that point may be lost —
same durability semantics as a sudden power loss on a physical
machine. For durable writes, workloads should `fsync` important data
themselves.

| Flag | Description |
|------|-------------|
| `-f`, `--force` | Force terminate immediately. Pending writes may be lost |
| `-t`, `--timeout` | Seconds to wait for graceful shutdown before force-killing. Defaults to 10 |
| `-q`, `--quiet` | Suppress progress output |

## msb exec

Execute a command inside a running sandbox.

```bash
msb exec devbox -- python -c "print('hello')"
msb exec devbox -- ls -la /app
```

| Flag | Description |
|------|-------------|
| `-t`, `--tty` | Allocate a pseudo-terminal (enables colors, line editing) |
| `-e`, `--env` | Set an environment variable (`KEY=VALUE`) |
| `-w`, `--workdir` | Override working directory |
| `-u`, `--user` | Run the command as the specified guest user |
| `--timeout` | Kill the command after this duration (e.g. `30s`, `5m`, `1h`) |
| `--rlimit` | Set a POSIX resource limit (e.g. `nofile=1024`, `nproc=64`) |
| `-q`, `--quiet` | Suppress progress output |

<Tip>
  The CLI auto-detects whether stdin is a terminal. When interactive, `msb exec` uses `attach` mode (TTY, line editing). When piped, it captures output. No `-i` flag is needed.
</Tip>

## msb copy

Copy files between the host and a sandbox.

```bash
msb copy ./local.txt devbox:/tmp/local.txt
msb copy devbox:/tmp/out.txt ./out.txt
msb copy devbox:/tmp/a devbox:/tmp/b
msb copy devbox:/tmp/a otherbox:/tmp/a
```

Host-to-sandbox and sandbox-to-host forms follow `docker cp` syntax. Same-sandbox and cross-sandbox forms are microsandbox extensions.

`msb copy` connects to an existing sandbox and starts it temporarily if needed, following the same lifecycle ownership behavior as `msb exec`. The shorter `msb cp` form is available as an alias.

| Flag | Description |
|------|-------------|
| `-q`, `--quiet` | Suppress progress output |

## msb logs

Read captured output from a sandbox. Each sandbox stores its captured stdio under `<sandbox-dir>/logs/exec.log` as JSON Lines, plus runtime/kernel diagnostics in `runtime.log`/`kernel.log`. The command works on running and stopped sandboxes alike — there is no protocol traffic, just a file read.

```bash
# Captured user-program output (default sources: stdout + stderr + output)
msb logs devbox

# Tail and follow
msb logs devbox --tail 100
msb logs devbox -f --grep ERROR

# Time-bounded
msb logs devbox --since 5m
msb logs devbox --since 2026-04-30T20:00:00Z --until 5m

# JSON Lines passthrough — feed to jq, vector, etc.
msb logs devbox --json | jq 'select(.s == "stderr")'

# Multi-session view: prefix each line with [id:N] so you can tell sessions apart
msb logs devbox --show-id

# Color each session's output a distinct color (implies --show-id)
msb logs devbox --color-sessions

# Include runtime/kernel diagnostics
msb logs devbox --source system
msb logs devbox --source all                # everything, chronologically merged
```

| Flag | Description |
|------|-------------|
| `--tail` | Show only the last N entries |
| `--since` | Show entries at or after this time (RFC 3339 or relative `5m`/`2h`/`1d`) |
| `--until` | Show entries strictly before this time (same formats) |
| `-f`, `--follow` | Follow in real time (200 ms polling, rotation-aware) |
| `--timestamps` | Prefix each line with the entry timestamp |
| `--source` | Sources to include: `stdout`, `stderr`, `output`, `system`, `all`. Repeat or comma-separate. Default: `stdout,stderr,output` |
| `--grep` | Client-side regex filter on the entry body |
| `--json` | Emit raw JSON Lines without decoding (one entry per line) |
| `--raw` | Opt into base64 encoding for non-UTF-8 bytes |
| `--show-id` | Prefix each line with `[id:N]` (the session correlation id) |
| `--color-sessions` | Color each session's lines a distinct color (implies `--show-id`) |
| `--color` | ANSI handling: `auto` (default), `always`, `never` |
| `--no-color` | Alias for `--color=never` |

**Source tags** (the `s` field in `--json` output):

- `stdout` / `stderr` — captured from the session's pipes when running in **pipe mode** (streams stay separated end to end).
- `output` — captured from the session in **pty mode**. pty allocation merges stdout and stderr at the kernel level inside the guest, so they arrive as a single stream — tagged `output` rather than mislabelled as `stdout`.
- `system` — synthetic lifecycle markers (`--- sandbox started ---` / `--- sandbox stopped ---`) plus diagnostic lines from `runtime.log`/`kernel.log` when `--source system` is requested.

<Tip>
  If a sandbox failed to start, `msb logs` prepends a styled error block reconstructed from `boot-error.json`. The block tells you the failure stage, errno, and a hint — even though no user-program output was ever captured.
</Tip>

## msb ls

List all stored sandboxes.

```bash
msb ls                    # All sandboxes (running and stopped)
msb ls --running          # Running sandboxes only
msb ls --stopped          # Stopped sandboxes only
msb ls --format json      # JSON output
msb ls -q                 # Names only
```

| Flag | Description |
|------|-------------|
| `--running` | Show only running sandboxes |
| `--stopped` | Show only stopped sandboxes |
| `--format` | Output format (`json`) |
| `-q`, `--quiet` | Show only sandbox names |

## msb status / ps

Show sandbox status with process details.

```bash
msb ps                    # Running sandboxes
msb ps my-app             # Single sandbox
msb ps -a                 # All sandboxes (including stopped)
msb ps --format json      # JSON output
```

| Flag | Description |
|------|-------------|
| `-a`, `--all` | Show all sandboxes, not just running ones |
| `--format` | Output format (`json`) |
| `-q`, `--quiet` | Show only sandbox names |

## msb metrics

Show live CPU, memory, disk, and network metrics for running sandboxes.

```bash
msb metrics               # All running sandboxes
msb metrics my-app        # Single sandbox
msb metrics --format json # JSON output
```

| Flag | Description |
|------|-------------|
| `--format` | Output format (`json`) |

## msb inspect

Show detailed configuration and status.

```bash
msb inspect devbox
msb inspect devbox --format json
```

| Flag | Description |
|------|-------------|
| `--format` | Output format (`json`) |

## msb rm

Remove one or more sandboxes and their associated state.

```bash
msb rm devbox
msb rm --force devbox     # Stop and remove in one step
msb rm worker-1 worker-2  # Remove multiple
```

| Flag | Description |
|------|-------------|
| `-f`, `--force` | Stop the sandbox if running, then remove it |
| `-q`, `--quiet` | Suppress progress output |

## msb install

Install a sandbox as a system command. Creates an executable in `~/.microsandbox/bin/` that launches `msb run` with the specified image and options.

```bash
msb install ubuntu                   # Install as 'ubuntu' command
msb install --name nodebox node      # Custom command name
msb install --tmp alpine             # Fresh sandbox every invocation
msb install -c 2 -m 1G python  # With resource limits
msb install --list                   # List installed commands
```

| Flag | Description |
|------|-------------|
| `-n`, `--name` | Command name for the alias (defaults to image name) |
| `-c`, `--cpus` | Number of virtual CPUs to allocate |
| `-m`, `--memory` | Amount of memory (e.g. `512M`, `1G`) |
| `-v`, `--volume` | Mount a host path or named volume (`SOURCE:DEST[:OPTIONS]`, e.g. `./src:/app:ro,noexec`) |
| `-w`, `--workdir` | Working directory inside the sandbox |
| `--shell` | Shell for interactive sessions |
| `-e`, `--env` | Set an environment variable (`KEY=VALUE`) |
| `-f`, `--force` | Overwrite an existing alias with the same name |
| `--no-pull` | Don't pull the image before installing |
| `--tmp` | Create a fresh sandbox on every invocation (no persistent state) |
| `-l`, `--list` | List all installed sandbox commands |

## msb uninstall

Remove an installed sandbox command.

```bash
msb uninstall nodebox
msb uninstall ubuntu alpine   # Remove multiple
```

## msb self

Manage the msb installation itself.

```bash
msb self update               # Update msb and libkrunfw to latest
msb self update --force       # Re-download even if up to date
msb self uninstall            # Remove msb (with confirmation prompt)
msb self uninstall --yes      # Skip confirmation
```

| Subcommand | Description |
|------------|-------------|
| `update` (alias: `upgrade`) | Update msb and libkrunfw to the latest release |
| `uninstall` | Remove msb, libkrunfw, and shell configuration |

| Flag | Subcommand | Description |
|------|------------|-------------|
| `-f`, `--force` | `update` | Re-download even if already on the latest version |
| `-y`, `--yes` | `uninstall` | Skip confirmation prompt |
