mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-24 06:34:33 +08:00
Document the shared inline and reusable-template resource model, driver behavior, migration from legacy template resource sections, and updated SDK examples. Part of #2838 Signed-off-by: Evan Lezar <elezar@nvidia.com>
859 lines
39 KiB
Plaintext
859 lines
39 KiB
Plaintext
---
|
||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||
# SPDX-License-Identifier: Apache-2.0
|
||
title: "Manage Sandboxes"
|
||
sidebar-title: "Sandboxes"
|
||
description: "Create sandboxes, understand sandbox isolation, and manage the full sandbox lifecycle."
|
||
keywords: "Generative AI, Cybersecurity, Gateway, Sandboxing, AI Agents, Sandbox Management, CLI"
|
||
position: 1
|
||
---
|
||
|
||
A sandbox is the OpenShell data plane: a safe, private execution environment where an AI agent runs. Each sandbox combines runtime isolation with OpenShell policy controls that prevent unauthorized data access, credential exposure, and network exfiltration.
|
||
|
||
<Info>You need an active gateway before creating a sandbox.</Info>
|
||
|
||
## Create a Sandbox
|
||
|
||
Create a sandbox with a single command. For example, to create a sandbox with Claude, run:
|
||
|
||
```shell
|
||
openshell sandbox create -- claude
|
||
```
|
||
|
||
The trailing command is the sandbox's canonical main process. OpenShell starts
|
||
it once, streams its output, and returns its exit status. Exit code 0 leaves a
|
||
retained sandbox in `Completed`; a nonzero exit leaves it in `Error` with a
|
||
`MainProcessFailed` condition. With no trailing command, OpenShell starts a
|
||
login shell in a retained pseudo-terminal: `/bin/bash -l` when the image
|
||
provides bash, otherwise a shell detected in the image such as `/bin/sh` on
|
||
minimal bases like Alpine. Add `--detach` to create the sandbox without
|
||
attaching:
|
||
|
||
```shell
|
||
openshell sandbox create --name worker --detach -- ./worker
|
||
```
|
||
|
||
Detached commands have no attachment grace period. When the command exits,
|
||
OpenShell records its terminal phase immediately. For a foreground command,
|
||
the create request declares one expected SSH attachment. OpenShell retains the
|
||
terminal transport until that connection drains and closes naturally, then
|
||
finalizes ephemeral cleanup.
|
||
|
||
Use `--no-keep` for an ephemeral command. OpenShell drains stdout and stderr,
|
||
captures the command result, and deletes the sandbox after the command exits:
|
||
|
||
```shell
|
||
openshell sandbox create --no-keep -- sh -c 'echo done; exit 0'
|
||
```
|
||
|
||
Combine `--detach` and `--no-keep` for a background workload whose lifecycle
|
||
belongs entirely to the gateway:
|
||
|
||
```shell
|
||
openshell sandbox create --detach --no-keep -- ./worker
|
||
```
|
||
|
||
The create command returns after the workload is ready. The gateway keeps the
|
||
canonical process running without a host-side attachment and deletes the
|
||
sandbox after that process exits.
|
||
|
||
`--upload` cannot yet be combined with a trailing main command because uploads
|
||
finish after the canonical process starts. Create a scratch sandbox, upload the
|
||
files, then launch the workload with `sandbox exec`, or build the files into the
|
||
sandbox image.
|
||
|
||
For automation, use `--output json` or `--output yaml` to get machine-readable sandbox metadata after creation:
|
||
|
||
```shell
|
||
openshell sandbox create --output json
|
||
```
|
||
|
||
Every sandbox requires a gateway. Register or select one before running sandbox commands:
|
||
|
||
```shell
|
||
openshell gateway add http://127.0.0.1:18080 --local --name local
|
||
openshell gateway select local
|
||
```
|
||
|
||
### CPU and Memory
|
||
|
||
Set portable CPU and memory quantities with `--cpu` and `--memory`:
|
||
|
||
```shell
|
||
openshell sandbox create --cpu 2 --memory 4Gi -- claude
|
||
```
|
||
|
||
CPU values use Kubernetes-style quantities such as `500m`, `1`, or `2.5`.
|
||
Memory values use byte quantities such as `512Mi`, `4Gi`, or `8G`.
|
||
|
||
OpenShell stores these values as typed portable resource requirements. Docker
|
||
and Podman apply them as runtime limits. Kubernetes uses each quantity as its
|
||
limit and, unless its driver configuration provides a request, as its request.
|
||
The VM and MXC drivers reject
|
||
CPU or memory requirements because they do not currently support per-sandbox
|
||
sizing. For VM gateways, configure `vcpus` and `mem_mib` on the driver instead.
|
||
|
||
API and SDK callers set `cpu.quantity` and `memory.quantity`. Workloads that
|
||
need Kubernetes requests below those portable quantities can set requests in
|
||
the Kubernetes driver configuration. A driver-config request takes precedence
|
||
over the quantity-to-request fallback.
|
||
|
||
<Warning>
|
||
CPU and memory values under `SandboxTemplate.resources.limits` or
|
||
`SandboxTemplate.resources.requests` are no longer supported. API clients must
|
||
use `SandboxSpec.resource_requirements.cpu.quantity` and
|
||
`SandboxSpec.resource_requirements.memory.quantity`. The gateway
|
||
rejects legacy requests with a migration error.
|
||
</Warning>
|
||
|
||
### Driver-Specific Configuration
|
||
|
||
Pass experimental driver-owned settings with `--driver-config-json`. The value
|
||
must be a JSON object keyed by driver name. The gateway forwards only the block
|
||
for its configured compute driver:
|
||
|
||
Nested keys inside each driver block use snake_case. The top-level envelope keys
|
||
are driver names, such as `kubernetes`, and are not part of the nested schema.
|
||
|
||
```shell
|
||
openshell sandbox create \
|
||
--driver-config-json '{"kubernetes":{"pod":{"runtime_class_name":"kata-containers","node_selector":{"pool":"gpu"}}}}' \
|
||
-- claude
|
||
```
|
||
|
||
Use this only for driver-specific fields that do not have a stable CLI flag.
|
||
Prefer stable flags such as `--cpu`, `--memory`, and `--gpu` when they cover
|
||
the same behavior.
|
||
|
||
### GPU Resources
|
||
|
||
To request GPU resources, add `--gpu`:
|
||
|
||
```shell
|
||
openshell sandbox create --gpu -- claude
|
||
```
|
||
|
||
Request a specific number of GPUs by passing a count to `--gpu`:
|
||
|
||
```shell
|
||
openshell sandbox create --gpu 2 -- claude
|
||
```
|
||
|
||
When you omit the count, OpenShell treats the request as `--gpu 1`.
|
||
Kubernetes honors counted `--gpu` requests by setting the `nvidia.com/gpu`
|
||
limit. Docker and Podman select the requested number of default NVIDIA CDI
|
||
devices in round-robin order. VM gateways accept only one GPU, either through
|
||
`--gpu` or `--gpu 1`; a single `gpu_device_ids` entry works with either form.
|
||
|
||
For Docker-backed sandboxes, GPU injection uses Docker CDI. If you enable Docker
|
||
CDI after the gateway starts, restart the gateway so OpenShell can detect the
|
||
updated Docker daemon capability.
|
||
|
||
Docker and Podman refresh the CDI inventory before validating or creating a
|
||
default GPU request, so CDI devices added or removed after the driver starts can
|
||
be reflected in later sandbox creates. On WSL2 all-only runtimes, the default
|
||
can fall back to `nvidia.com/gpu=all`; that fallback counts as one selectable
|
||
device.
|
||
|
||
Exact GPU device selection is driver-specific and still requires `--gpu`. For
|
||
Docker or Podman, pass CDI IDs through `cdi_devices`. The top-level key must
|
||
match the active driver; replace `docker` with `podman` when using Podman. CDI
|
||
IDs are treated as opaque strings. The list must not contain duplicate IDs, and
|
||
its length must match the effective GPU count:
|
||
|
||
```shell
|
||
openshell sandbox create \
|
||
--gpu \
|
||
--driver-config-json '{"docker":{"cdi_devices":["nvidia.com/gpu=0"]}}' \
|
||
-- claude
|
||
```
|
||
|
||
### Custom Containers
|
||
|
||
Use `--from` to create a sandbox from the base image, another pre-built sandbox name, a rootfs tar archive, or a container image:
|
||
|
||
```shell
|
||
openshell sandbox create --from base
|
||
openshell sandbox create --from ollama
|
||
openshell sandbox create --from ./rootfs.tar
|
||
openshell sandbox create --from my-registry.example.com/my-image:latest
|
||
```
|
||
|
||
Bare names such as `base` and `ollama` resolve to images under `ghcr.io/nvidia/openshell-community/sandboxes`. Set `OPENSHELL_COMMUNITY_REGISTRY` when you need to use an internal mirror.
|
||
|
||
`--from` does not build local Dockerfiles or directories. Build and tag the
|
||
image with the container engine used by your local gateway, then pass the
|
||
resulting image reference:
|
||
|
||
```shell
|
||
# Docker gateway
|
||
docker build -t my-image:latest .
|
||
openshell sandbox create --from my-image:latest
|
||
|
||
# Podman gateway
|
||
podman build -t localhost/my-image:latest .
|
||
openshell sandbox create --from localhost/my-image:latest
|
||
```
|
||
|
||
For a remote gateway, push the image to a registry that the gateway can pull
|
||
from and use that registry image reference.
|
||
|
||
<Warning>
|
||
**Pre-0.1.0 breaking change:** `openshell sandbox create --from ./Dockerfile`
|
||
and directory sources no longer build images. Build and tag the image before
|
||
you create the sandbox.
|
||
</Warning>
|
||
|
||
#### Rootfs Tar Archives
|
||
|
||
A rootfs tar archive (`.tar`, `.tar.gz`, `.tgz`) is a flat filesystem produced
|
||
by `docker export`, `podman export`, or `buildah mount` plus `tar`. It lets you
|
||
create a sandbox without a registry or a running image daemon:
|
||
|
||
```shell
|
||
docker create --name export-me my-image:latest
|
||
docker export -o rootfs.tar export-me
|
||
docker rm export-me
|
||
|
||
openshell sandbox create --from ./rootfs.tar
|
||
```
|
||
|
||
Rootfs tar sources require a local gateway running the VM compute driver. The
|
||
CLI asks the gateway for a staging slot, writes the archive to the location the
|
||
gateway allocates, and passes back a single-use token; the gateway resolves that
|
||
token to a path for the driver. Because the CLI writes the archive directly to
|
||
the gateway host's filesystem, the two must share a filesystem and run as the
|
||
same user. Gateways using the Docker, Podman, or Kubernetes drivers reject
|
||
rootfs tar sources.
|
||
|
||
Gzip-compressed archives (`.tar.gz`, `.tgz`) are decompressed while the gateway
|
||
stages them, so the sandbox sees the same filesystem either way. Compression is
|
||
detected from the archive contents, not the file name.
|
||
|
||
The gateway caps archive size (10 GiB by default, configurable with the VM
|
||
driver's `rootfs_tar_max_bytes`), and reclaims an unused staging slot after 30
|
||
minutes. The cap applies to the expanded archive too: a compressed source that
|
||
decompresses past the limit is rejected.
|
||
|
||
## Reuse Workload Templates
|
||
|
||
Sandbox workload templates let workspace admins define reusable runtime shapes for a workspace. A template stores the image, environment, resource requests, and driver-specific configuration that sandboxes should inherit. When you create a sandbox from a template, the create request can still attach providers, labels, and policy, but the workload comes from the named template.
|
||
|
||
Create a template:
|
||
|
||
```shell
|
||
openshell sandbox template create gpu-kata \
|
||
--image registry.example.com/agent:latest \
|
||
--cpu 2 \
|
||
--memory 4Gi \
|
||
--gpu 1 \
|
||
--label team=runtime \
|
||
--env FEATURE_FLAG=on
|
||
```
|
||
|
||
Use `--gpu` without a count when the template should request the active
|
||
driver's default GPU assignment. Use `--gpu COUNT` when the template needs a
|
||
specific number of GPUs.
|
||
|
||
Add driver-specific settings when the active compute driver needs them:
|
||
|
||
```shell
|
||
openshell sandbox template create gpu-kata \
|
||
--image registry.example.com/agent:latest \
|
||
--driver-config-json '{"kubernetes":{"pod":{"runtime_class_name":"kata-containers","node_selector":{"pool":"gpu"}}}}'
|
||
```
|
||
|
||
If you omit `--image`, the gateway applies its default sandbox image when a sandbox is created from the template. Use this when the template should only define resource, environment, or driver settings.
|
||
|
||
Create a sandbox from a template:
|
||
|
||
```shell
|
||
openshell sandbox create --template gpu-kata --provider github -- claude
|
||
```
|
||
|
||
When the gateway creates a sandbox from a template, it resolves the template's
|
||
CPU, memory, and GPU settings into the sandbox's typed `resource_requirements`.
|
||
The reusable template field `SandboxWorkloadConfig.resources` uses the same
|
||
`ResourceRequirements` message as `SandboxSpec.resource_requirements`, so API
|
||
and SDK callers use `cpu.quantity`, `memory.quantity`, and `gpu` in both places.
|
||
When loading a reusable template stored with the earlier `cpu`, `memory`, and
|
||
`gpu` shape, the gateway converts it to the current typed requirements before
|
||
returning or using the template.
|
||
|
||
The `--template` flag cannot be combined with inline workload flags such as `--from`, `--cpu`, `--memory`, `--gpu`, `--env`, or `--driver-config-json`. Put those values on the template instead. Create-time policy and provider attachments remain part of the sandbox request, so each sandbox can keep its own access boundary.
|
||
|
||
Inspect and manage templates:
|
||
|
||
```shell
|
||
openshell sandbox template list
|
||
openshell sandbox template list --label-selector team=runtime
|
||
openshell sandbox template get gpu-kata
|
||
openshell sandbox template delete gpu-kata
|
||
```
|
||
|
||
Use `--all-workspaces` with `sandbox template list` when you need an admin view across workspaces:
|
||
|
||
```shell
|
||
openshell sandbox template list --all-workspaces
|
||
```
|
||
|
||
For JSON or YAML, template list output contains `templates` and
|
||
`next_page_token` fields. Pass the returned token to `--page-token` to
|
||
continue.
|
||
|
||
## Base Sandbox Container
|
||
|
||
The `base` sandbox container is the default runtime image for standard OpenShell sandboxes unless the gateway overrides its default sandbox image. It is published as `ghcr.io/nvidia/openshell-community/sandboxes/base:latest` and maintained in the [OpenShell Community](https://github.com/NVIDIA/OpenShell-Community/tree/main/sandboxes/base) repository.
|
||
|
||
The base container includes common development tooling, supported agent CLIs, and the default sandbox policy. Use it when you want a general-purpose agent environment without a workflow-specific image:
|
||
|
||
```shell
|
||
openshell sandbox create --from base
|
||
```
|
||
|
||
For default policy coverage by agent, refer to [Default Policy](/reference/default-policy). For the supported agent list, refer to [Supported Agents](/about/supported-agents).
|
||
|
||
## Connect to a Sandbox
|
||
|
||
Attach to the canonical main process in a running sandbox:
|
||
|
||
```shell
|
||
openshell sandbox connect my-sandbox
|
||
```
|
||
|
||
Disconnecting does not stop the process or close its stdin. A later `connect`
|
||
attaches to the same process instance and replays up to 1 MiB of recent
|
||
output. One attachment owns stdin at a time. Use `sandbox exec --tty --
|
||
/bin/bash -l` when you want a new independent shell instead.
|
||
|
||
Press `Ctrl-P`, then `Ctrl-Q` to disconnect without terminating the main
|
||
process. `Ctrl-C` retains its normal terminal behavior and interrupts the
|
||
foreground process.
|
||
|
||
Launch VS Code or Cursor directly into the sandbox workspace:
|
||
|
||
```shell
|
||
openshell sandbox create --editor vscode --name my-sandbox
|
||
openshell sandbox connect my-sandbox --editor cursor
|
||
```
|
||
|
||
When `--editor` is used, OpenShell keeps the sandbox alive and installs an
|
||
OpenShell-managed SSH include file instead of cluttering your main
|
||
`~/.ssh/config` with generated host blocks.
|
||
|
||
## Execute a Command in a Sandbox
|
||
|
||
Run a one-shot command inside a running sandbox without opening an interactive shell:
|
||
|
||
```shell
|
||
openshell sandbox exec -n my-sandbox -- ls -la /workspace
|
||
```
|
||
|
||
Pipe stdin into the command:
|
||
|
||
```shell
|
||
echo "hello" | openshell sandbox exec -n my-sandbox -- cat
|
||
```
|
||
|
||
The command's exit code is propagated to the CLI, so `exec` works in scripts that check return codes.
|
||
|
||
Run an interactive shell with a TTY:
|
||
|
||
```shell
|
||
openshell sandbox exec -n my-sandbox --tty -- /bin/bash
|
||
```
|
||
|
||
OpenShell allocates a TTY automatically when both stdin and stdout are terminals. Force the behavior with `--tty` or disable it with `--no-tty`.
|
||
|
||
| Flag | Purpose |
|
||
| ----------------- | -------------------------------------------------------- |
|
||
| `-n`, `--name` | Sandbox to target. |
|
||
| `--workdir` | Working directory for the command inside the sandbox. |
|
||
| `--timeout` | Command timeout in seconds. `0` disables the timeout. |
|
||
| `--tty` | Force TTY allocation. |
|
||
| `--no-tty` | Disable TTY allocation even when attached to a terminal. |
|
||
| `--no-login-shell`| Run the command without sourcing shell login startup files. |
|
||
| `--env` | Set an environment variable for the command (`KEY=VALUE`, repeatable). |
|
||
|
||
### Skip shell startup files
|
||
|
||
By default `sandbox exec` runs the command through a login shell (`bash -lc`), so the sandbox user's first available `.bash_profile`, `.bash_login`, or `.profile` is sourced first (and `.bashrc` only if that login file sources it). This makes tool-specific environment configuration available automatically, which suits interactive and tool-discovery use.
|
||
|
||
For automation and managed checks that need predictable output and side effects, pass `--no-login-shell` so those startup files are not sourced before the command runs:
|
||
|
||
```shell
|
||
openshell sandbox exec -n my-sandbox --no-login-shell -- /usr/local/bin/managed-probe
|
||
```
|
||
|
||
In this mode a sandbox user's login startup files cannot write to the command's output, create files, or otherwise affect the requested command before it starts. The command still runs under `bash -c`, which reads `BASH_ENV` if it is set in the command's environment. The default (login-shell) behavior is unchanged when the flag is omitted.
|
||
|
||
## Set Environment Variables
|
||
|
||
Inject environment variables into the sandbox at creation time:
|
||
|
||
```shell
|
||
openshell sandbox create --env API_KEY=sk-test --env DEBUG=1 -- my-agent
|
||
```
|
||
|
||
Variables set with `--env` are available to all processes in the sandbox, including the initial command, interactive shells, and exec commands.
|
||
|
||
When an `--env` key looks like a credential — a known provider variable, or a name whose underscore-separated segments include a credential word such as `TOKEN`, `SECRET`, `PASSWORD`, `CREDENTIAL`, `API_KEY`, `ACCESS_KEY`, or `SECRET_KEY` (for example `DB_TOKEN` or `MY_ACCESS_KEY`) — `sandbox create` prints a non-blocking warning. Matching is on whole segments, so unrelated names like `TOKENIZERS_PARALLELISM` or `PASSWORDLESS_LOGIN` do not warn. The agent inside the sandbox can read plain environment values directly, so to hide a secret from the agent, attach it through a [profile-backed provider](/providers/profiles) with `--provider` instead. Suppress the warning with `--no-credential-warnings`. Detection uses the key name only; values are never inspected or printed.
|
||
|
||
You can also set per-command environment variables with `sandbox exec`:
|
||
|
||
```shell
|
||
openshell sandbox exec -n my-sandbox --env MY_VAR=hello -- printenv MY_VAR
|
||
```
|
||
|
||
Per-command variables override the sandbox-level environment for that command only.
|
||
|
||
Environment variable names starting with `OPENSHELL_` are reserved. Keys must match `[A-Za-z_][A-Za-z0-9_]*`.
|
||
|
||
## Label a Sandbox
|
||
|
||
Attach labels when you create a sandbox to track ownership, environment, or workflow grouping:
|
||
|
||
```shell
|
||
openshell sandbox create --label env=dev --label team=platform -- claude
|
||
```
|
||
|
||
List only the sandboxes that match a label selector:
|
||
|
||
```shell
|
||
openshell sandbox list --selector env=dev
|
||
openshell sandbox list --selector env=dev,team=platform
|
||
```
|
||
|
||
The Python SDK accepts the same gateway labels and selectors. Labels passed to
|
||
`create` are stored on the gateway sandbox object and are returned on
|
||
`SandboxRef.labels`, so selector-based listing finds Python-created sandboxes:
|
||
|
||
```python
|
||
from openshell import SandboxClient
|
||
|
||
with SandboxClient.from_active_cluster() as client:
|
||
sandbox = client.create(workspace="default", name="deep-research-1", labels={"env": "dev", "team": "platform"})
|
||
assert sandbox.labels["team"] == "platform"
|
||
|
||
matches = client.list_all(workspace="default", label_selector="env=dev,team=platform")
|
||
assert sandbox.id in {s.id for s in matches}
|
||
```
|
||
|
||
Python SDK `list` methods return a lazy `Pager` whose iteration yields one
|
||
`Page` per gateway request. Use `list_all` only when you want to exhaust the
|
||
collection; pass `page_token` to resume from a token saved from an earlier page.
|
||
|
||
Create reusable sandbox templates through the Python SDK when several runs
|
||
should share the same workload shape:
|
||
|
||
```python
|
||
from openshell import SandboxClient
|
||
|
||
with SandboxClient.from_active_cluster() as client:
|
||
templates = client.sandbox_templates()
|
||
|
||
templates.create(
|
||
workspace="default",
|
||
name="python",
|
||
image="ghcr.io/nvidia/openshell-community/sandboxes/python:latest",
|
||
)
|
||
|
||
sandbox = client.create_from_template(workspace="default", template_name="python")
|
||
```
|
||
|
||
For non-interactive automation, pass a renewable client-credentials provider.
|
||
Omitted issuer, client ID, audience, and scopes are read from the active
|
||
gateway's metadata. The client requires TLS for non-loopback gateways:
|
||
|
||
```python
|
||
from openshell import ClientCredentialsAuth, SandboxClient
|
||
|
||
auth = ClientCredentialsAuth(client_secret=lambda: load_secret())
|
||
with SandboxClient.from_active_cluster(client_credentials=auth) as client:
|
||
sandboxes = client.list_all(workspace="default")
|
||
```
|
||
|
||
## Expose Long Running Services
|
||
|
||
Service forwarding makes a long-running process inside a sandbox reachable through a gateway-managed URL. Use it for development servers, notebooks, dashboards, or other services that keep listening after the sandbox starts. Run the service on loopback inside the sandbox, expose its port, then open the URL printed by OpenShell.
|
||
|
||
Expose a service that listens on loopback inside the sandbox:
|
||
|
||
```shell
|
||
openshell service expose my-sandbox 8080
|
||
```
|
||
|
||
Pass an optional service name to create a named service URL:
|
||
|
||
```shell
|
||
openshell service expose my-sandbox 8080 web
|
||
```
|
||
|
||
List exposed endpoints:
|
||
|
||
```shell
|
||
openshell service list
|
||
```
|
||
|
||
List endpoints for one sandbox:
|
||
|
||
```shell
|
||
openshell service list my-sandbox
|
||
```
|
||
|
||
Use structured output for automation:
|
||
|
||
```shell
|
||
openshell service list --output json
|
||
openshell service list my-sandbox --output yaml
|
||
```
|
||
|
||
Structured list output contains `services` and `next_page_token` fields. Each
|
||
record contains `workspace`, `sandbox`, `service`, `target_port`, and `url`.
|
||
The unnamed service uses an empty `service` string. Pass the returned token to
|
||
`--page-token` to continue. An empty result has an empty `services` collection.
|
||
|
||
Show or delete one endpoint:
|
||
|
||
```shell
|
||
openshell service get my-sandbox web
|
||
openshell service delete my-sandbox web
|
||
```
|
||
|
||
Omit the service name to manage the unnamed endpoint:
|
||
|
||
```shell
|
||
openshell service get my-sandbox
|
||
openshell service delete my-sandbox
|
||
```
|
||
|
||
<Note>
|
||
Loopback gateways return local `openshell.localhost` URLs. Remote gateways return HTTPS URLs that require normal gateway authentication. For gateway service-domain configuration, refer to [Manage Gateways](/sandboxes/manage-gateways#configure-service-forwarding).
|
||
</Note>
|
||
|
||
## Monitor and Debug
|
||
|
||
List all sandboxes:
|
||
|
||
```shell
|
||
openshell sandbox list
|
||
```
|
||
|
||
Filter the list by labels when you want a narrower view:
|
||
|
||
```shell
|
||
openshell sandbox list --selector team=platform
|
||
```
|
||
|
||
Use `-o json` or `-o yaml` for machine-readable output:
|
||
|
||
```shell
|
||
openshell sandbox list -o json
|
||
openshell sandbox list -o yaml
|
||
```
|
||
|
||
Structured list output contains `sandboxes` and `next_page_token` fields.
|
||
Pass the returned token to `--page-token` to continue.
|
||
|
||
Get detailed information about a specific sandbox. The output lists **Policy source** (`sandbox` or `global`), **Revision** (the active policy’s row version for that source), and the formatted active policy YAML:
|
||
|
||
```shell
|
||
openshell sandbox get my-sandbox
|
||
```
|
||
|
||
For automation, use `--output json` or `--output yaml` to get machine-readable sandbox details:
|
||
|
||
```shell
|
||
openshell sandbox get my-sandbox --output json
|
||
```
|
||
|
||
Print only the policy YAML for scripting (same effective policy, no metadata):
|
||
|
||
```shell
|
||
openshell sandbox get my-sandbox --policy-only
|
||
```
|
||
|
||
### Diagnose Failed Calls to Tool Servers
|
||
|
||
A sandbox can be `Ready` while an agent's call to an external tool server fails with `fetch failed`. Run `openshell sandbox get my-sandbox` and look under `Tool server connections` for the server's address, `Last result`, and `Reported at`. This information is available for configured endpoints that use MCP over HTTP. It helps you find where the call failed without changing sandbox readiness.
|
||
|
||
JSON and YAML output include an `endpoint_statuses` list with `last_reported_at`. The protobuf API exposes the corresponding timestamp as `Sandbox.status.endpoint_statuses[].last_reported_time`; SDKs render it using their native or curated time representation. Select the endpoint by its address to read the result directly. Treat `endpoint_id` as opaque.
|
||
|
||
For example, use `jq` to select the tool server at `tools.example.com`, path `/mcp`, and port `443`:
|
||
|
||
```shell
|
||
openshell sandbox get my-sandbox --output json | jq -e \
|
||
--arg host tools.example.com --arg path /mcp --argjson ports '[443]' '
|
||
.endpoint_statuses[]
|
||
| select(.host == $host and .path == $path and .ports == $ports)
|
||
'
|
||
```
|
||
|
||
Read `last_result` to choose the next check:
|
||
|
||
- `NoObservedExchange`: no result has been reported for the current configuration and supervisor session. Try the operation and inspect its logs if no result appears.
|
||
- `HttpResponseReceived`: the server returned a final HTTP status below 400, including a protocol upgrade. Informational responses alone do not establish success. Check the client's response for protocol or tool errors; an HTTP 200 response can still contain an error.
|
||
- `PolicyDenied`: OpenShell denied the request, including MCP protocol-version or request-body policy checks. Check the sandbox policy and denial logs.
|
||
- `CredentialUnavailable`: required credentials were unavailable. Check the endpoint's attached provider.
|
||
- `TlsFailed`: TLS setup or the handshake failed. Check certificates and TLS configuration.
|
||
- `TransportFailed`: the network exchange failed. Check name resolution, connectivity, and the server process.
|
||
- `UpstreamRejected`: the server returned an HTTP status of 400 or higher. Check the server's response and logs.
|
||
- `Unspecified`: the result is unrecognized or absent. Do not infer success from it.
|
||
|
||
For example, a stopped server can produce `TransportFailed` while the sandbox stays `Ready`. After the server recovers, an HTTP response below 400 updates the result to `HttpResponseReceived`.
|
||
|
||
Results come from actual traffic and do not expire. A result can remain unchanged after the server stops until another observed exchange or configuration/session reset. Reports combine recent observations and can drop them when full, so this list is not a request history. The address remains available when a result resets to `NoObservedExchange`. To verify current tool availability, run the actual operation.
|
||
|
||
`last_reported_at` is the RFC 3339 rendering of the protobuf `last_reported_time` when the gateway accepted the result. It is empty until a result is reported. New accepted observations advance it, including repeated results; identical report retries do not. If a configuration reset supersedes a report whose acknowledgement was lost, the gateway can accept still-valid pending evidence again and advance this timestamp without another exchange.
|
||
|
||
Addresses use lowercase hosts, canonical paths, and sorted distinct effective ports. Equivalent addresses share one record that combines observations from all callers and ports; it does not establish that every caller or port works. A failure before the HTTP path is known, such as a TLS failure during CONNECT, updates status only when the host and port identify one distinct endpoint. If several configured paths share that host and port, inspect the logs for the failure.
|
||
|
||
### Inspect Logs and Activity
|
||
|
||
Stream sandbox logs to monitor agent activity and diagnose policy decisions:
|
||
|
||
```shell
|
||
openshell logs my-sandbox
|
||
```
|
||
|
||
| Flag | Purpose | Example |
|
||
| ---------- | ---------------------------- | ---------------------------------- |
|
||
| `--tail` | Stream logs in real time | `openshell logs my-sandbox --tail` |
|
||
| `--source` | Filter by log source | `--source sandbox` |
|
||
| `--level` | Filter by severity | `--level warn` |
|
||
| `--since` | Show logs from a time window | `--since 5m` |
|
||
|
||
OpenShell Terminal combines sandbox status and live logs in a single real-time dashboard:
|
||
|
||
```shell
|
||
openshell term
|
||
```
|
||
|
||
Use the terminal to spot blocked connections marked `action=deny` and provider-related proxy activity. If a connection is blocked unexpectedly, add the host to your network policy or update the attached provider profile. Refer to [Policies](/sandboxes/policies) for the workflow.
|
||
|
||
The dashboard has three panels stacked vertically: Gateways, Providers (or Global Settings), and Sandboxes. Navigate within a panel with `Up`/`Down` or `j`/`k`. At a list boundary the cursor overflows into the adjacent panel, skipping empty panels. Use `Tab`/`Shift+Tab` to cycle panels directly. Press `h`/`l` or `Left`/`Right` in the middle panel to switch between the Providers and Global Settings tabs.
|
||
|
||
The sandbox table’s NOTES column shows `Invalid config` when policy or provider configuration blocks provisioning. Open the sandbox detail view for the full rejection reason, or run `openshell sandbox get <name> -o json`. The note clears after the configuration is repaired; active port forwards remain listed.
|
||
|
||
## Port Forwarding
|
||
|
||
Forward a local port to a running sandbox to access services inside it, such as a web server or database:
|
||
|
||
```shell
|
||
openshell forward start 8000 my-sandbox
|
||
openshell forward start 8000 my-sandbox -d # run in background
|
||
```
|
||
|
||
OpenShell prints the local URL only after the forward listener is reachable. Background forwards must be tracked locally so `openshell forward list` and `openshell forward stop` can manage them.
|
||
|
||
List and stop active forwards:
|
||
|
||
```shell
|
||
openshell forward list
|
||
openshell forward stop 8000 my-sandbox
|
||
```
|
||
|
||
Use JSON or YAML output when inspecting tracked forwards from automation:
|
||
|
||
```shell
|
||
openshell forward list --output json
|
||
openshell forward list --output yaml
|
||
```
|
||
|
||
Structured output includes `sandbox`, `bind_address`, `port`, `pid`, and
|
||
`alive`. The `alive` boolean reports whether the tracked PID still matches the
|
||
expected OpenShell SSH forward; it does not probe the forwarded socket. When no
|
||
forwards are tracked, structured output returns an empty collection.
|
||
|
||
The default table colorizes the `STATUS` column only when both standard output and standard error are capable ANSI terminals; piping or redirecting either stream, or running under `TERM=dumb`, gives a plain-text table. Other styled output—including `-v` log lines, progress spinners, prompts, and error messages—is decided per stream, so redirecting one stream leaves the other styled. Prefer `--output json` for automation rather than matching on the table. Set `NO_COLOR` to any non-empty value or pass `--color never` to suppress ANSI formatting, and `--color always` to force it when piping into a pager. `--color` applies to every `openshell` command.
|
||
|
||
<Tip>
|
||
You can also forward a port at creation time with `--forward`:
|
||
|
||
```shell
|
||
openshell sandbox create --forward 8000 -- claude
|
||
```
|
||
|
||
</Tip>
|
||
|
||
## SSH Config
|
||
|
||
Generate an SSH config entry for a sandbox so tools like VS Code Remote-SSH can connect directly:
|
||
|
||
```shell
|
||
openshell sandbox ssh-config my-sandbox
|
||
```
|
||
|
||
Append the output to `~/.ssh/config` or use `--editor` on `sandbox create`/`sandbox connect` for automatic setup.
|
||
|
||
## Transfer Files
|
||
|
||
Upload files from your host into the sandbox:
|
||
|
||
```shell
|
||
openshell sandbox upload my-sandbox ./src
|
||
```
|
||
|
||
When you omit the destination, OpenShell discovers the sandbox's working
|
||
directory and uploads there. For a named local directory, OpenShell preserves
|
||
the basename, matching `scp -r` and `cp -r`. If that directory already exists,
|
||
the upload merges into it and overwrites matching entries without deleting
|
||
unrelated entries.
|
||
|
||
OpenShell preserves symlinks during upload. A symlink arrives in the sandbox as a symlink with the same target path instead of an expanded copy of the target file or directory. Dangling symlinks are also preserved.
|
||
|
||
Download files from the sandbox to your host:
|
||
|
||
```shell
|
||
openshell sandbox download my-sandbox output ./local
|
||
```
|
||
|
||
When the sandbox-side source is a single file, the destination follows `cp`-style placement: if the destination already exists as a directory or ends with `/`, the file lands inside it as `<dest>/<basename>`; otherwise the file is written at the exact destination path.
|
||
|
||
The CLI discovers the sandbox's canonical working directory and only allows
|
||
sandbox-side sources that resolve inside it. Paths that escape lexically, such
|
||
as `/etc/passwd` or `/sandbox/../etc/passwd`, and paths that escape through a
|
||
symlink are refused before any data is transferred. Relative sources are
|
||
resolved from the working directory; absolute sources within the same canonical
|
||
directory are also accepted.
|
||
|
||
<Note>
|
||
You can also upload files at creation time with the `--upload` flag on
|
||
`openshell sandbox create`. Pass `--upload` multiple times to upload
|
||
several paths in a single command:
|
||
|
||
```shell
|
||
openshell sandbox create --upload ./src:/workspace/src --upload ./config:/workspace/config -- claude
|
||
```
|
||
|
||
</Note>
|
||
|
||
By default, uploads inside a Git repository respect `.gitignore` rules so that
|
||
build artifacts, dependency caches, and other ignored files are not transferred.
|
||
If `.gitignore` filtering excludes every file in the upload path, the CLI falls
|
||
back to an unfiltered upload and prints a warning. Pass `--no-git-ignore` to
|
||
opt into unfiltered uploads explicitly, upload a path outside the Git work
|
||
tree, or force-add the intended files if they should remain Git-aware.
|
||
|
||
## Stop and Start Sandboxes
|
||
|
||
Stop compute when you want to retain a sandbox and its persistent workspace
|
||
without keeping its container, pod, or VM running:
|
||
|
||
```shell
|
||
openshell sandbox stop my-sandbox
|
||
openshell sandbox start my-sandbox
|
||
```
|
||
|
||
The name is optional and defaults to the last-used sandbox. Stop stops local
|
||
background forwards and waits for the `Stopped` phase. Start waits until the
|
||
same sandbox returns to `Ready`. While stopped, you cannot connect, execute
|
||
commands, transfer files, forward ports, or reach exposed services. Policies,
|
||
provider attachments, settings, service definitions, and persistent workspace
|
||
data remain associated with the sandbox.
|
||
|
||
Stop and start are idempotent. You can also start a retained `Completed` or
|
||
`Error/MainProcessFailed` sandbox to launch a fresh instance of its canonical
|
||
command. Starting a fresh instance invalidates SSH sessions issued for the
|
||
previous runtime generation. Delete an inactive sandbox normally when you no
|
||
longer need its retained state.
|
||
|
||
## Delete Sandboxes
|
||
|
||
Deleting a sandbox stops all processes, releases resources, and purges injected credentials.
|
||
|
||
The command can return while cleanup is pending. `deletion accepted` means the
|
||
gateway started deletion; inspect the sandbox until it disappears if your next
|
||
step requires completion. An already-absent sandbox is a successful no-op, but
|
||
missing workspaces and authorization failures remain errors. SDK callers can
|
||
inspect [typed deletion outcomes](/reference/api-errors#deletion-outcomes).
|
||
|
||
```shell
|
||
openshell sandbox delete my-sandbox
|
||
```
|
||
|
||
Delete multiple sandboxes by listing their names in one command:
|
||
|
||
```shell
|
||
openshell sandbox delete sandbox-a sandbox-b
|
||
```
|
||
|
||
When a multi-sandbox delete fails for one entry, the CLI reports that sandbox's
|
||
failure and continues with the remaining names. The command exits with an error
|
||
after it attempts every requested deletion if any entry failed.
|
||
|
||
## Sandbox Lifecycle
|
||
|
||
Every sandbox moves through a defined set of phases:
|
||
|
||
Before workload activation, OpenShell validates the effective policy and matching
|
||
provider configuration. A rejection keeps the workload unstarted and exposes a
|
||
`ConfigurationInvalid` condition in `Provisioning`. Use `openshell sandbox get`
|
||
to inspect the diagnostic, then [repair the policy or provider configuration](/sandboxes/policies#validation-failures).
|
||
Management operations remain available while startup is blocked. After repair,
|
||
the supervisor completes startup without recreating the sandbox. Starting a
|
||
stopped sandbox repeats configuration admission before launching its workload.
|
||
|
||
The gateway enforces a 300-second provisioning repair window, independently of
|
||
the CLI wait timeout. An effective policy, settings, provider, profile, or
|
||
attachment change resets the window from its stored change time. The first
|
||
failed configuration load for that change grants another full window. Repeated
|
||
failures and reconnects do not extend it; reaching `Ready` clears it.
|
||
|
||
When the window expires, the sandbox enters `Error` with reason
|
||
`ProvisioningTimedOut`. The gateway stops its workload and supervisor compute,
|
||
retaining the sandbox record, diagnostic, and restartable storage. Cleanup can
|
||
remain pending if the backend is unavailable; the gateway retries it. Inspect
|
||
`provisioning` in JSON output for the deadline, timeout, and cleanup timestamps.
|
||
TUI NOTES distinguishes pending cleanup from reclaimed compute.
|
||
|
||
Repair the configuration, wait for cleanup to complete, then explicitly restart:
|
||
|
||
```shell
|
||
openshell sandbox get my-sandbox --output json
|
||
openshell sandbox start my-sandbox
|
||
```
|
||
|
||
Editing configuration after expiry does not restart compute. A retry gets a new
|
||
300-second window, while static-policy restrictions from any previous activation
|
||
remain in force. Timed-out records are retained even for ephemeral creates; use
|
||
`sandbox delete` when you no longer need the diagnostic or stored state.
|
||
|
||
| Phase | Description |
|
||
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| Provisioning | The runtime is setting up the sandbox environment, or the gateway is waiting for the sandbox supervisor to establish its authenticated control session. |
|
||
| Ready | The sandbox is running and its supervisor control session is connected. You can connect, execute commands, sync files, and view logs. |
|
||
| Stopping | The gateway accepted a stop request and is stopping compute while retaining persistent state. |
|
||
| Stopped | Compute was stopped explicitly and access is unavailable. |
|
||
| Starting | Compute is starting. The sandbox becomes usable only after a fresh supervisor session connects. |
|
||
| Completed | The canonical main process exited with code 0. Its normalized result is available in `status.exit_code`. |
|
||
| Error | The canonical main process failed, or sandbox infrastructure failed. Inspect the condition reason and `status.exit_code`. |
|
||
| Deleting | The sandbox is being torn down. The system releases resources and purges credentials. |
|
||
|
||
The compute backend can become ready before the sandbox supervisor connects to
|
||
the gateway. During this interval, the sandbox remains in `Provisioning` and
|
||
reports a `Ready=False` condition with the reason `SupervisorNotConnected`.
|
||
After a gateway restart, an existing sandbox can return to `Provisioning`
|
||
temporarily while its supervisor reconnects. Wait for the phase to return to
|
||
`Ready` before you connect to the sandbox or execute commands.
|
||
|
||
The gateway records a successful canonical main-process exit as `Ready=False`
|
||
with reason `MainProcessCompleted`. Nonzero and signal-normalized results use
|
||
`MainProcessFailed` and the `Error` phase. It also sets `status.exit_code`;
|
||
signal exits use the standard `128 + signal` convention. Compute runtimes do
|
||
not automatically restart that process.
|
||
|
||
## Sandbox Compute Drivers
|
||
|
||
The gateway's configured compute driver determines how OpenShell creates each sandbox. The CLI workflow stays the same across drivers: you create, connect to, inspect, and delete sandboxes through the gateway API.
|
||
|
||
For Docker, Podman, MicroVM, and Kubernetes behavior, refer to [Sandbox Compute Drivers](/reference/sandbox-compute-drivers).
|
||
|
||
## Next Steps
|
||
|
||
- To follow a complete end-to-end example, refer to the [GitHub Sandbox](/get-started/tutorials/github-sandbox) tutorial.
|
||
- To select a workspace or understand access roles, refer to [Manage Workspaces and Access](/sandboxes/manage-workspaces).
|
||
- To supply API keys or tokens, refer to [Manage Providers](/sandboxes/manage-providers).
|
||
- To control what the agent can access, refer to [Policies](/sandboxes/policies).
|
||
- To use the default runtime image, refer to [Base Sandbox Container](#base-sandbox-container).
|