1
0
mirror of https://github.com/docker/docs.git synced 2026-10-23 18:09:02 +03:00

docs: Cloud Sandboxes API and SDK (#26168)

## Description

Add an experimental Sandboxes API and SDK section alongside Docker
Sandboxes under AI and agents, at `/ai/sandboxes-api/`. Cover
installation of `@docker/sandboxes`, browser and PAT authentication, and
a TypeScript walkthrough that launches the shell kit, runs a command,
and deletes the sandbox. Explain kits and image choices with examples,
along with compute sizes, quotas, permissions, resource lifecycle,
errors, and retries. Link subscription setup and billing to the shared
Docker Agentic Platform guide. Preserve the previous
`/ai/sandboxes/api/` URLs with redirects.

Uses the shared Docker Agentic Platform documentation merged in #26142.
Sibling to #26158, which introduces CLI-side Cloud Sandboxes
documentation.

Include 36 SDK cookbook recipes at `/ai/sandboxes-api/cookbook/` and the
public REST API reference at `/reference/api/sandboxes/`. Import the
generated export from `docker/sbx-api` commit `60d79c1d` (source
`1a7f4026`). Overview and installation remain maintained here. The
reference uses the shared API renderer from main, with separate
operation and schema pages, streaming and WebSocket details, and a
downloadable OpenAPI spec. An API reference link appears in the API/SDK
sidebar. A pinned manifest and import script provide the basis for later
release automation.

Examples are optional in the shared renderer; supplied examples still
have to pass schema validation. Requests without body examples use a
request-body file with instructions to prepare it. The import copies all
selected recipes and the OpenAPI specification byte for byte from the
corrected upstream export, without patches or link rewrites. The broken
naming recipe remains temporarily excluded.

This remains a draft pending validation against the published npm SDK.
The imported cookbook contains the TypeScript-only guidance, five-minute
request deadlines, kit-readiness explanation, active-policy status
requirement, and snapshot-deletion and lifetime-renewal restrictions.

Validation: scoped Markdown and Vale checks, full Hugo build, rendered
navigation, redirects, and internal links, and TypeScript type-checking
against the generated public SDK from `docker/sbx-api` commit
`68f214b9`. Import validation also covers all 60 REST operations and 188
schema definitions, 1,198 imported-page links and anchors,
desktop/mobile rendering, Markdown output, spec delivery, and a repeat
import. The shared renderer tests, strict generation for all five APIs
(zero diagnostics), full Hugo build, and verification of 453
HTML/Markdown page pairs and API links pass. No live sandbox execution.
The corrected export passes strict generation for all five APIs with
zero diagnostics, scoped Markdown/Vale lint, a full Hugo build, and
verification of 453 HTML/Markdown page pairs and API links. A repeat
import produces identical files. The spec update changes examples only;
schemas and operations are unchanged.

@netlify /ai/sandboxes-api/

Generated by Codex

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
David Karlsson
2026-09-24 18:06:30 +02:00
committed by GitHub
parent 80bd6569e5
commit bed22bda81
69 changed files with 15059 additions and 43 deletions

View File

@@ -86,6 +86,15 @@ params:
| time | Estimated time to complete |
| prerequisites | Prerequisites or "None" |
### Catalog navigation
Set `params.sidebar.activeChildrenOnly: true` on a section to show only its
active child in the sidebar. Other children remain available in search and
the sitemap. Sidebar group headings are omitted in this mode.
For a recipe catalog, set `params.recipeCatalog: true` on the section to add
a **Browse all recipes** link to each recipe page.
## Shortcodes
Shortcodes are reusable components that add rich functionality to your
@@ -341,6 +350,18 @@ Create card layouts for organizing content.
*/>}}
```
### Recipe lists
List a section's recipes by their `params.sidebar.group` value:
```markdown
{{</* recipe-list group="Working in a sandbox" */>}}
```
The list uses each page's sidebar title and description, ordered by page
weight. It renders in two columns on larger screens and one column on smaller
screens. The Markdown output includes the same links and descriptions.
### Icons
Use Material Symbols icons in your content.

View File

@@ -24,6 +24,10 @@ params:
description: Run AI coding agents in isolated environments.
icon: command-line
link: /ai/sandboxes/
- title: Sandboxes API and SDK
description: Create and manage cloud sandboxes from your applications.
icon: code-bracket
link: /ai/sandboxes-api/
- title: MCP Catalog and Toolkit
description: Augment your AI workflow with MCP servers.
icon: /icons/toolkit.svg

View File

@@ -0,0 +1,62 @@
---
title: Docker Sandboxes API and SDK
linkTitle: Sandboxes API and SDK
description: Use the Docker Sandboxes API and TypeScript SDK to manage cloud sandboxes programmatically.
keywords: docker sandboxes API, cloud sandboxes API, sandbox SDK, TypeScript SDK, JavaScript
weight: 15
params:
apiReferenceBanner: false
sidebar:
group: AI and agents
badge:
color: violet
text: Experimental
aliases:
- /ai/sandboxes/api/
---
> [!NOTE]
> The Docker Sandboxes API and SDK are experimental. Features, interfaces,
> and behavior may change.
Use the Docker Sandboxes API to create cloud sandboxes, run commands, and
transfer files from your applications and automated workflows. You can also
manage related resources, including images, snapshots, volumes, and secrets.
To try it, [run your first cloud sandbox](get-started.md) with the TypeScript
SDK. The tutorial uses a bundled kit that supplies a shell environment for
running commands.
## Activate cloud access
To use the API or SDK, [activate a Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access).
Use the same Docker account to [authenticate your application](authentication.md).
Cloud compute is billed separately from your Docker subscription. See
[Billing](/manuals/agentic-platform/signup.md#billing) for details.
## Choose an interface
Use the TypeScript SDK in your JavaScript or TypeScript application. The SDK
provides typed requests and responses, waits for sandboxes to start or stop,
and handles file transfers and interactive processes.
See [Install the SDK](install.md) for installation instructions.
You can also call the REST API directly from any language or HTTP tool.
See the [API reference](/reference/api/sandboxes/latest/) for operations,
request fields, responses, and the downloadable OpenAPI specification.
To run agents from your terminal, see [Docker Sandboxes](../sandboxes/_index.md).
## Develop your application
- [SDK cookbook](cookbook/_index.md): follow examples for processes, files,
storage, networking, and other sandbox operations.
- [API concepts](concepts.md): choose a kit or image, identify resources, and
wait for actions to finish.
- [Authentication and authorization](authentication.md): authenticate requests
and understand which permissions your application needs.
- [Errors and retries](errors.md): handle failures and retry requests without
duplicating work.
- [Compute sizes and limits](limits.md): choose resources and handle account
quotas and request rate limits.

View File

@@ -0,0 +1,154 @@
---
title: Authentication and authorization
description: Authenticate requests to the Docker Sandboxes API with access tokens and learn which permissions your application needs.
keywords: docker sandboxes API authentication, sandbox authorization, bearer token, sandbox permissions, owner scope
weight: 30
aliases:
- /ai/sandboxes/api/authentication/
---
> [!NOTE]
> The Docker Sandboxes API and SDK are experimental. Features, interfaces,
> and behavior may change.
Use browser sign-in when running an application interactively, or a personal
access token (PAT) for automation. The SDK obtains short-lived access tokens
and renews them as needed.
You need an active [Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access).
Authenticate with the Docker account you used to subscribe.
## Sign in through your browser
Use the OAuth helper to sign in with your Docker account:
```typescript
import { oauth, Sandboxes } from '@docker/sandboxes';
const auth = oauth({
onVerification({ verificationUriComplete, verificationUri, userCode }) {
console.log(`Open ${verificationUriComplete ?? verificationUri}`);
console.log(`Verification code: ${userCode}`);
},
});
await auth.getAccessToken();
const client = new Sandboxes({ auth });
```
Open the printed URL and complete sign-in. Browser sign-in supports single
sign-on and two-factor authentication. The `getAccessToken()` call waits for
you to finish before the program continues.
The SDK keeps credentials in memory and refreshes them while your application
runs. With the default configuration, you sign in again each time you start
the application. SDK sign-in is separate from `docker login` and `sbx login`.
## Authenticate automation with a PAT
Use a [personal access token](/manuals/security/access-tokens/personal-access-tokens.md)
for CI jobs and unattended applications. When creating the token, select the
`sandbox:use` permission in your Docker account's personal access token
settings. Registry permissions alone don't grant Cloud Sandboxes access.
Provide your Docker ID and PAT to the SDK. For example, read them from your
application's environment:
```typescript
import { pat, Sandboxes } from '@docker/sandboxes';
const username = process.env.DOCKER_ID;
const personalAccessToken = process.env.DOCKER_PAT;
if (!username || !personalAccessToken) {
throw new Error('Set DOCKER_ID and DOCKER_PAT');
}
const client = new Sandboxes({
auth: pat({ username, personalAccessToken }),
});
```
The SDK exchanges the PAT for a short-lived access token and repeats the
exchange when needed. If the PAT is invalid or revoked, authentication fails.
Store the PAT in your CI or application's secret store and keep it out of
source control and logs.
## Authenticate agents
An AI agent needs credentials for its model provider in addition to your
Docker sign-in. For example, Claude Code can use an Anthropic API key, and
Codex can use an OpenAI API key.
Store the provider key as a secret and attach it when creating the sandbox.
For example, with an authenticated `client` and an Anthropic API key in
`providerKey`:
```typescript
const secret = await client.secrets.create({
displayName: 'anthropic-key',
serviceType: 'anthropic',
token: { value: providerKey },
});
const sandbox = await client.kits.launchAndWait('claude', {
storage: { secrets: [secret.name] },
});
```
The secret is attached before the agent runs. Keep the key out of command
arguments, source files, and plain environment variables inside the sandbox.
## Resource access and permissions
Your credentials determine which account's resources you can access. Cloud
uses them to identify the account, so leave the optional `parent` field empty
in requests.
Each request also checks whether you have permission for the action on the
target resource. For example, creating a sandbox requires `sandboxesCreate`,
reading it requires `sandboxesRead`, and deleting it requires
`sandboxesDelete`.
Account permissions also control access to optional features. See
[Supported Cloud options](concepts.md#choose-supported-cloud-options).
## Authenticate direct API requests
If you call the REST API without the SDK, obtain and renew access tokens in
your application. Exchange your Docker ID and a PAT with `sandbox:use`
permission using the [Docker Hub authentication API](/reference/api/hub/latest/operations/AuthCreateAccessToken/):
```console
$ ACCESS_TOKEN=$(curl --silent --show-error --fail --request POST \
--url https://hub.docker.com/v2/auth/token \
--header "Content-Type: application/json" \
--data '{"identifier":"<DOCKER_ID>","secret":"<PERSONAL_ACCESS_TOKEN>"}' \
| jq -er '.access_token')
```
Send the returned access token in the `Authorization` header when calling
`https://connect.docker.com/sandboxes`:
```http
Authorization: Bearer <access_token>
```
The Sandboxes API accepts the access token returned by the exchange, not the
PAT itself.
If you manage access tokens but use the SDK for requests, import `bearer` from
`@docker/sandboxes` and configure the client with `auth: bearer(accessToken)`.
Create a client with a fresh token when the previous token expires.
## Authenticate sandbox requests
The SDK handles authentication when you run commands or transfer files using
a sandbox's methods. It obtains a token scoped to that sandbox and the
operation, then reuses or renews the token as needed.
For example, running a command requires `sandboxesExec` and obtaining its
token requires `sandboxesCredential`. Your account must have both permissions.
If you call a sandbox endpoint directly, use a token issued for that sandbox.
Don't send the Docker Hub token used for management requests to a sandbox
endpoint. See [Management and sandbox endpoints](concepts.md#management-and-sandbox-endpoints).

View File

@@ -0,0 +1,200 @@
---
title: Docker Sandboxes API concepts
linkTitle: API concepts
description: Learn how kits and images define a cloud sandbox, how to connect to it, and how to manage resources throughout their lifecycle.
keywords: docker sandboxes API concepts, sandbox kits, sandbox images, cloud sandbox endpoint, API operations
weight: 20
aliases:
- /ai/sandboxes/api/concepts/
---
> [!NOTE]
> The Docker Sandboxes API and SDK are experimental. Features, interfaces,
> and behavior may change.
An application uses the Docker Sandboxes API to create sandboxes, connect to
them, and track their state. Choose the environment for your sandbox, then
learn how to work with its resources throughout their lifecycle.
## Kits and sandbox images
A sandbox kit defines an environment for an agent or tool, including its
image, setup, network rules, and credential requirements. The SDK bundles a
catalog of kits you can launch by name. Using kits from other sources
requires preparing their content for the API.
You can also create a sandbox from a container image. Choose the source based
on how much of the environment you want to configure yourself:
| Source | What it provides | How to create a sandbox |
| --- | --- | --- |
| [Bundled kit](#bundled-kits) | An image reference and configuration included in the SDK's catalog | `client.kits.launch('shell')` |
| Custom kit | An environment defined by a kit you obtain separately | `client.create()` with [prepared kit artifacts](#supply-kit-artifacts) |
| Registry image (`imageRef`) | A container image to use with your own sandbox settings | `client.create({ imageRef: 'ubuntu:24.04', resources: 'small' })` |
| Image resource (`image`) | An image already prepared for Cloud Sandboxes, including its compute settings | `client.create({ image: 'images/<uid>' })` |
The `imageRef` value is an image name in a registry. The `image` value is a
resource name returned by the Sandboxes API. When you use `image`, omit
`resources` because the image resource supplies its compute settings.
These creation methods don't wait for the sandbox to be running. See
[Wait for an action to finish](#wait-for-an-action-to-finish) before running
commands.
### Bundled kits
The npm package includes the following kit definitions and supporting files.
Launch a bundled kit by its short name, such as `shell`:
| Kit name | Environment |
| --- | --- |
| `shell` | A shell environment for running your own commands |
| `claude` | Claude Code |
| `codex` | Codex |
| `cursor` | Cursor |
| `devin` | Devin |
| `docker-agent` | Docker Agent |
| `gemini` | Gemini CLI |
| `opencode` | OpenCode |
For example, `client.kits.launchAndWait('shell')` creates a shell sandbox and
waits until it's running. The kit launch helpers default to `small` compute,
with two CPUs and 4 GiB of memory. See [Compute sizes](limits.md#compute-sizes)
to choose a different size.
Bundled kits are tied to the SDK release. Call `client.kits.list()` to see
the catalog in your installed version. The kit definitions are included in
the npm package, so the SDK doesn't download them from a registry. Cloud
Sandboxes pulls their referenced container images as needed.
To run an AI agent, provide credentials for its model provider, such as an
Anthropic API key for Claude Code. See
[Authenticate agents](authentication.md#authenticate-agents). The `shell` kit
needs no provider key to run commands.
## Resource names
Use a resource's returned `name` to refer to it in later requests. A sandbox
name has the form `sandboxes/<uid>`. Pass the complete name, including the
`sandboxes/` prefix, when reading or deleting it.
The server assigns the name, which stays the same throughout the resource's
lifetime. The optional `displayName` is a label you can change without
changing the resource's identity.
## Wait for an action to finish
Wait until a sandbox is running before sending commands to it. In the SDK,
`client.kits.launchAndWait()` creates a bundled kit's sandbox and waits for it
to run. If you use `client.create()` or `client.kits.launch()`, call
`waitUntilRunning()` on the returned sandbox and use the result to run commands.
For direct API requests, HTTP 202 means the action was accepted and is still
in progress. Read the resource repeatedly until it reaches the state you need.
Sandbox creation can continue after your client stops waiting. Read the
sandbox again to check its state, and inspect its `failure` field if it has
failed. See [Errors and retries](errors.md) for how to recover.
Deletion can also take time. The API returns HTTP 202 while the sandbox is
being deleted and HTTP 204 when deletion is complete. After deletion,
authorized reads return `notFound`.
### Wait for kit setup
`waitUntilRunning()` and `kits.launchAndWait()` wait for the sandbox to reach
the `running` state. Kit setup commands, such as installing tools or cloning
a repository, can still be running at that point.
The SDK doesn't provide a helper that waits for all kit setup to finish. If
your application depends on that setup, add a readiness check before starting
its work. What you check depends on the kit and the task—for example, a
completion marker written after a repository clone finishes, or a successful
health check from a service.
Poll with a delay between checks and a timeout so your application stops
waiting if setup fails.
## Management and sandbox endpoints
Creating a sandbox and running a command inside it use different endpoints:
| Endpoint | Use it to |
| --- | --- |
| Management API at `https://connect.docker.com/sandboxes` | Create, inspect, and delete sandboxes and manage related resources. |
| Sandbox API at the returned `core.endpoint.uri` | Run processes and read or write files inside that sandbox. |
The SDK builds request URLs from these base URLs. If you make HTTP requests
directly, append the `/v1` route to the base URL, preserving any existing path.
For example, the management route `/v1/sandboxes` becomes
`https://connect.docker.com/sandboxes/v1/sandboxes`.
Each sandbox endpoint requires a token that grants access to that sandbox.
The SDK obtains this token when you use a sandbox's process or file methods. See
[Authentication and authorization](authentication.md) for details.
A sandbox's endpoint can change when its runtime changes. Read the sandbox
resource again before reconnecting to get its endpoint.
## Read all results from a list
List requests return one page of results at a time. To retrieve the next page,
pass the response's `nextPageToken` as the next request's `pageToken`. Keep the
same page size, filter, and ordering. Continue until `nextPageToken` is empty,
even if a page contains fewer items than you requested.
The default page size is 25 for Cloud sandbox, image, snapshot, volume, and
secret lists. You can request up to 100 items per page.
## Choose supported Cloud options
Cloud supports kits, sandbox timeouts, stored secrets, and volume attachments,
subject to account permissions and feature availability. For example, volume
access must be enabled for your account. An SDK method's presence doesn't
guarantee that your account can use it.
## Supply kit artifacts
To use a kit outside the bundled catalog, your application must load and
prepare its content before calling `client.create()`. The npm SDK doesn't
fetch kits from a registry. Its `kits.launch()` and `kits.launchAndWait()`
helpers accept only bundled kit names.
For example, the [Hermes agent kit](https://hub.docker.com/r/sbx/hermes-agent-kit)
is published as `docker.io/sbx/hermes-agent-kit:latest`. To use it through the
SDK, you need code outside the SDK that loads the kit definition and its
supporting files into the serialized
[v2 artifact format](https://github.com/docker/sbx-kits-contrib/blob/v0.17.0/spec/types.go)
accepted by the API.
The `kits` array holds the sandbox kit and any mixin kits that add
configuration to it. See [Kits](../sandboxes/customize/kits.md) for how these kinds of
kits work together. The bundled launch helpers prepare this same input for
the kits in their catalog.
Pass the kit's source reference and prepared artifact bytes to
`client.create()`:
```typescript
function createFromKit(reference: string, artifactBytes: Uint8Array) {
return client.create({
resources: 'small',
kits: [
{
artifact: {
ref: { ref: reference, kind: 'sandbox' },
inline: artifactBytes,
},
},
],
});
}
```
The `ref` identifies the kit's source. It doesn't trigger a registry pull.
The `inline` value contains the serialized artifact as a `Uint8Array`,
including the kit's file content. Raw `spec.yaml`, ZIP files, and OCI manifests
aren't valid inputs for this field.
To launch a public kit by registry reference without writing loading code,
use the [Docker Agentic Platform Console](/manuals/agentic-platform/kits.md#run-a-kit-by-reference).

View File

@@ -0,0 +1,62 @@
---
title: Docker Sandboxes SDK cookbook
linkTitle: Cookbook
description: Build agent workflows with the Docker Sandboxes SDK for TypeScript.
keywords: cloud sandboxes, sandboxes sdk, typescript, agent kits
weight: 60
params:
recipeCatalog: true
sidebar:
activeChildrenOnly: true
---
Use these recipes to add file transfers, processes, storage, and other sandbox
operations to your application. Each recipe shows the relevant SDK calls and
an expandable complete TypeScript example.
Start with [Get started](../get-started.md) to run your first sandbox, or
[install the SDK](../install.md) to use these examples in an existing project.
For HTTP operations and request fields, see the
[API reference](/reference/api/sandboxes/latest/).
## Get started
Authenticate and launch a kit before exploring individual SDK operations.
{{< recipe-list group="Get started" >}}
## Working in a sandbox
Use a running sandbox for commands, project files, and web applications.
{{< recipe-list group="Working in a sandbox" >}}
## Packaging and state
Keep an environment or its state for later work.
{{< recipe-list group="Packaging and state" >}}
## Security and policy
Give agents the access they need without embedding credentials in application code.
{{< recipe-list group="Security and policy" >}}
## Connect and configure
Choose a different image, control lifetime, or connect additional storage and clients.
{{< recipe-list group="Connect and configure" >}}
## Requests and responses
Handle request options and failures deliberately.
{{< recipe-list group="Requests and responses" >}}
## Long-running work
Continue work across lost connections or coordinate several sandboxes.
{{< recipe-list group="Long-running work" >}}

View File

@@ -0,0 +1,48 @@
---
title: "Add a Docker credential for cloud sandboxes"
linkTitle: "Add a Docker credential for cloud sandboxes"
description: "Exchange a Docker OIDC identity token for a credential that the service stores for your sandboxes to use."
keywords: "cloud sandboxes, sandboxes api, add a docker credential for cloud sandboxes"
weight: 404
params:
sidebar:
group: "Security and policy"
---
Store a Docker credential for workloads that need Docker access. This is separate from [signing your application in](connect-to-cloud-with-a-bearer-token.md): the application's access token authenticates SDK calls, while this exchange stores a credential for sandboxes.
You need an authenticated client and a fresh Docker OpenID Connect identity token for the same identity. The example accepts that identity token; it does not perform the sign-in that issues it.
## Exchange the token {#1-exchange-the-token}
Pass the identity token to the identity collection. The service stores the resulting credential as `secrets/docker`, replacing its previous value. It returns no credential material.
Treat the identity token as single-use. If you lose the response, obtain a new identity token before trying again. Never log either token.
Use [stored secrets](get-a-stored-secret-into-a-sandbox.md) for other workload credentials. For an automation client that needs to call the SDK, use [PAT authentication](connect-to-cloud-with-a-bearer-token.md) instead of this exchange.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.identity.exchangeDockerCredential({ idToken });
```
<details>
<summary>Complete TypeScript example: credentials/exchange.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function exchangeDockerCredential(
client: Sandboxes,
idToken: string,
) {
return client.identity.exchangeDockerCredential({ idToken });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,151 @@
---
title: "Run agents with kits"
linkTitle: "Run agents with kits"
description: "Discover the available kits, launch an agent sandbox, and run work inside it."
keywords: "cloud sandboxes, sandboxes api, run agents with kits"
weight: 104
params:
sidebar:
group: "Get started"
---
A kit supplies an agent's image and configuration, including tools and the settings they need. Launching a kit gives you a repeatable starting point without assembling those settings in every application.
Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Agent workloads also need credentials for their model provider. Docker authentication gives access to sandboxes; it does not sign you in to Anthropic, OpenAI, or another agent provider.
## Discover available kits {#1-discover-available-kits}
List the kits bundled with your installed SDK. Use an entry's name when launching it. Listing the catalog does not create a sandbox.
The SDK's launch-by-name helper includes these starting points:
| Kit name | Environment |
| --- | --- |
| `claude` | Claude Code |
| `codex` | Codex |
| `cursor` | Cursor |
| `devin` | Devin |
| `docker-agent` | Docker Agent |
| `gemini` | Gemini CLI |
| `opencode` | OpenCode |
| `shell` | A shell environment for your own commands |
Use the catalog result as the list for your installed version. Upgrading the SDK can update the bundled kits.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.kits.list();
```
<details>
<summary>Complete TypeScript example: kits/catalog.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export function listKits(client: Sandboxes) {
return client.kits.list();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Launch a kit {#2-launch-a-kit}
To run Claude Code, choose `claude` and attach a stored `anthropic` secret containing your Anthropic API key. [Store the provider credential](get-a-stored-secret-into-a-sandbox.md) first, then pass the stored secret's resource name to the launch example. Keep credentials out of command arguments and plain environment values.
Pass a catalog name, a display name, and any stored secret names the agent needs. The example selects Small (2 vCPUs, 4 GiB) and uses the launch-and-wait helper to return a running sandbox under one deadline. Small is also the default when you omit kit resources. See [compute sizes](work-within-the-limits.md) for the other choices.
Use `launch` when you want the accepted handle immediately and will wait separately. If the wait fails, inspect the accepted sandbox retained by the error before launching another one.
`launchAndWait()` waits for the sandbox to reach the running state. A kit can still be installing tools or cloning a repository at that point. The SDK has no helper that waits for all kit setup to finish.
If your work depends on that setup, check the result it needs before starting. For example, a kit can write a completion marker after cloning a repository, or a service can expose a health check. Poll with a delay between checks and a timeout so failed setup does not leave your application waiting indefinitely.
The launch helper accepts only names returned by the bundled catalog, not community repository URLs or registry references. Account network policies still apply to kit sandboxes.
Use `shell` when you need an environment for scripts, builds, or your own executable. To use a container image directly, see [Run your own container image](run-your-own-container-image.md).
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.kits.launchAndWait(kitName, {
displayName,
resources: 'small',
storage: { secrets },
});
```
<details>
<summary>Complete TypeScript example: kits/launch.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function launchKit(
client: Sandboxes,
kitName: string,
displayName: string,
secrets: string[] = [],
) {
return client.kits.launchAndWait(kitName, {
displayName,
resources: 'small',
storage: { secrets },
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Run the agent {#3-run-the-agent}
Pass the sandbox handle returned by the launch example and your prompt to the run helper. It runs `claude -p` and waits for the response. Launching a kit alone does not submit an agent task.
The example returns captured output and an exit code. Check both when diagnosing an agent failure. For a long task, [stream output](run-something-that-produces-real-output.md); for a conversation in a terminal, [open an interactive session](run-an-interactive-shell-in-a-cloud-sandbox.md).
Save the sandbox name if you will resume the work later. [Delete the sandbox](delete-a-cloud-sandbox.md) when you no longer need its files or running processes.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.processes.run(
{ args: ['claude', '-p', prompt] },
{ timeoutMs: 300_000 },
);
```
<details>
<summary>Complete TypeScript example: kits/run.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function runKitAgent(sandbox: Sandbox, prompt: string) {
return sandbox.processes.run(
{ args: ['claude', '-p', prompt] },
{ timeoutMs: 300_000 },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Explore the wider kit catalog {#4-explore-the-wider-kit-catalog}
The [community kit catalog](https://github.com/docker/sbx-kits-contrib) includes agents such as Aider, Amp, Copilot, Kiro, and OpenHands, plus development tools, browser automation, source control, and security scanning. For example, Code Server adds a browser-based editor and Playwright adds browser automation.
Some kits define a complete sandbox environment; others add tools or configuration to an existing agent environment. These are not all bundled SDK launch targets. See the [Docker kits guide](https://docs.docker.com/ai/sandboxes/customize/kits/) for the catalog's usage instructions.

View File

@@ -0,0 +1,143 @@
---
title: "Attach persistent storage"
linkTitle: "Attach persistent storage"
description: "Create a volume, mount it into a new sandbox so its data outlives the sandbox, and delete the volume when you no longer need it."
keywords: "cloud sandboxes, sandboxes api, attach persistent storage"
weight: 505
params:
sidebar:
group: "Connect and configure"
---
Keep project data after a sandbox is deleted by mounting a persistent volume. A volume is an independent resource: deleting a sandbox does not delete the volume.
Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md), a managed image from [Register and manage an image](register-and-manage-an-image.md), and an absolute mount path such as `/workspace/data`.
## Create a volume {#1-create-a-volume}
Choose a display name and an idempotency key for the create request. Save the returned volume name, such as `volumes/…`. That name identifies the volume in later requests; its display name is only a label.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.volumes.create(
{ displayName: name },
{ idempotencyKey: requestId },
);
```
<details>
<summary>Complete TypeScript example: volumes/create.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createVolume(
client: Sandboxes,
name: string,
requestId: string,
) {
return client.volumes.create(
{ displayName: name },
{ idempotencyKey: requestId },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Mount it in a new sandbox {#2-mount-it-in-a-new-sandbox}
Supply the volume name and destination path when creating the sandbox. The example accepts a map of volume names to mount paths and waits for the sandbox to run. For Cloud Sandboxes, supply one entry and use exclusive attachment. Volume access must be enabled for your account; memory-snapshot images do not support volume attachment.
Attachments belong to the sandbox's creation settings. To mount the volume elsewhere, create another sandbox with the attachment. With exclusive attachment, release the first sandbox before attaching the volume to another one. Avoid overlapping mount paths.
Write a file under the mount path, delete the sandbox, then attach the volume to a new sandbox to read it again. Files outside the mount remain on the sandbox's own disk.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.create(
{
displayName: name,
image,
storage: {
volumes: [...mountPaths].map(([volume, target]) => ({
volume,
target,
})),
},
},
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: volumes/attach.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createWithVolumes(
client: Sandboxes,
name: string,
image: string,
mountPaths: Map<string, string>,
requestId: string,
) {
const sandbox = await client.create(
{
displayName: name,
image,
storage: {
volumes: [...mountPaths].map(([volume, target]) => ({
volume,
target,
})),
},
},
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Delete the volume {#3-delete-the-volume}
Delete the sandbox using the volume first, then delete the volume through its handle. If the service reports that it is still in use, wait for sandbox deletion to finish before retrying.
Deleting the volume permanently removes its contents. Copy out anything you need to keep.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await volume.delete();
```
<details>
<summary>Complete TypeScript example: volumes/delete.ts</summary>
```typescript
import type { Volume } from '@docker/sandboxes';
export async function deleteVolume(volume: Volume) {
await volume.delete();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,111 @@
---
title: "Clone a cloud sandbox"
linkTitle: "Clone a cloud sandbox"
description: "Read how a running sandbox was configured, then create a second sandbox with the same image and environment."
keywords: "cloud sandboxes, sandboxes api, clone a cloud sandbox"
weight: 702
params:
sidebar:
group: "Long-running work"
---
Create another sandbox using selected settings from an existing one. This example copies the image source and environment, not the sandbox's files or running processes. For a saved filesystem or memory state, use a [snapshot](snapshot-and-fork-a-sandbox.md).
You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), the source sandbox's resource name, and a display name for the new sandbox.
## Read the source configuration {#1-read-the-source-configuration}
Get the source sandbox and inspect its image and environment. Review environment values before copying them; they may contain credentials or settings specific to the original job.
Do not treat the complete response as a create request. It includes read-only state and connection details.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.get(name);
return sandbox.core;
```
<details>
<summary>Complete TypeScript example: clone/inspect.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function inspectSandbox(client: Sandboxes, name: string) {
const sandbox = await client.get(name);
return sandbox.core;
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Create a fresh sandbox {#2-create-a-fresh-sandbox}
The example copies the image source and environment. For a registry image it also copies resource settings. It waits for the new sandbox to run.
The call returns the new sandbox's handle, ready for running commands or transferring files.
This is a limited configuration copy, not a complete clone. It does not copy kits, attached policies, secrets, volumes, files, or process state. A copied image alone may not reproduce a kit's setup. For repeated agent environments, launch the same [named kit](add-tools-with-kits.md) with the options your application saved.
Attach required network policies and credentials before running work in the new sandbox. The source is unchanged, and both sandboxes need their own cleanup.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const copy = await client.create(
{
displayName: name,
...(core.image
? { image: core.image, agent: core.agent }
: {
imageRef: core.imageRef!,
resources: core.resources,
}),
environment: core.environment,
},
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return copy.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: clone/rebuild.ts</summary>
```typescript
import type { Sandbox, Sandboxes } from '@docker/sandboxes';
export async function rebuildSandbox(
client: Sandboxes,
core: Sandbox['core'],
name: string,
requestId: string,
) {
if (!core.image && !core.imageRef)
throw new TypeError('Source has no image');
const copy = await client.create(
{
displayName: name,
...(core.image
? { image: core.image, agent: core.agent }
: {
imageRef: core.imageRef!,
resources: core.resources,
}),
environment: core.environment,
},
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return copy.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,201 @@
---
title: "Authenticate to Docker"
linkTitle: "Authenticate to Docker"
description: "Sign in interactively, use a Docker personal access token, or supply an access token from your application."
keywords: "cloud sandboxes, sandboxes api, authenticate to docker"
weight: 101
params:
sidebar:
group: "Get started"
---
Choose an authenticator when your application creates its client. The client obtains a credential when its first request needs one, then uses it for requests to Docker Cloud Sandboxes. You need an [installed SDK](https://docs.docker.com/ai/sandboxes-api/install/) and a Docker account with Cloud Sandboxes access.
Choose interactive sign-in when running an example yourself. Use a personal access token (PAT) for a service or CI job. If your application already manages Docker access tokens, pass a token or a token provider.
## Sign in interactively {#1-sign-in-interactively}
The helper creates a client with an OAuth authenticator. Call an SDK method, such as listing sandboxes, to start sign-in. Open the printed verification URL in your browser and enter the displayed code. Sign in to your Docker account and approve the request. The SDK request proceeds when verification succeeds; a denied or expired request fails. The [complete program](run-a-complete-example.md) shows this flow from start to finish.
The request that starts sign-in also sets its deadline. Pass `{ timeoutMs: 300_000 }` as that call's request options to give yourself five minutes to sign in and complete the request.
The SDK holds credentials in memory by default and refreshes the access token as later requests need it, while refresh credentials remain valid. Close the client when finished to release SDK-owned resources. This does not delete sandboxes or revoke Docker sign-in.
You can pass the same authenticator to several clients to reuse their sign-in. Closing one client leaves the authenticator usable by the others.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const auth = oauth({
onVerification: ({ verificationUri, userCode }) => {
console.log(`Open ${verificationUri} and enter ${userCode}`);
},
});
return new Sandboxes({ auth });
```
<details>
<summary>Complete TypeScript example: cloudauth/login.ts</summary>
```typescript
import {
fileOAuthCredentialStore,
oauth,
Sandboxes,
} from '@docker/sandboxes';
export function login() {
const auth = oauth({
onVerification: ({ verificationUri, userCode }) => {
console.log(`Open ${verificationUri} and enter ${userCode}`);
},
});
return new Sandboxes({ auth });
}
export function loginWithSavedCredentials(path: string) {
const storedAuth = oauth({
store: fileOAuthCredentialStore({ path }),
onVerification: ({ verificationUri, userCode }) => {
console.log(`Open ${verificationUri} and enter ${userCode}`);
},
});
return new Sandboxes({ auth: storedAuth });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Optional: save credentials between runs {#2-optional-save-credentials-between-runs}
To avoid signing in each time a Node.js program starts, expand the complete example above and use `loginWithSavedCredentials(path)`. It passes `fileOAuthCredentialStore` to the OAuth authenticator.
The file store supports Node.js on POSIX systems, not browsers or Windows. Its file contains plaintext access and refresh credentials: keep it out of source control, use a private directory, and do not share it between processes. For a keychain or secret manager, implement `OAuthCredentialStore` with `load` and `save` instead.
Remove the stored credentials when your application no longer needs them. Your application owns that removal; closing a client does not remove the file or revoke the credentials.
## Use a personal access token {#3-use-a-personal-access-token}
Create a [Docker personal access token](https://docs.docker.com/security/access-tokens/) for the Docker account your application will use. Provide your Docker username and PAT through your application's secret manager or environment, then pass them to the SDK's PAT authentication option.
The SDK exchanges the PAT for a short-lived access token and repeats the exchange when needed. Supply the PAT as a PAT credential, not as an access token or an Authorization header. A revoked or expired PAT requires a replacement credential.
Managed OAuth and PAT authentication use the default Docker service address. Use a caller-supplied access token or provider when you need to override that address.
Your PAT needs the `sandbox:use` permission. Select it when you create the token, at `https://app.docker.com/accounts/[username]/settings/personal-access-tokens`.
Keep the PAT on the machine running your application. Do not put it in a sandbox's environment, source code, or logs.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return new Sandboxes({
auth: pat({ username, personalAccessToken }),
});
```
<details>
<summary>Complete TypeScript example: cloudauth/pat.ts</summary>
```typescript
import { pat, Sandboxes } from '@docker/sandboxes';
export function connectWithPAT(
username: string,
personalAccessToken: string,
) {
return new Sandboxes({
auth: pat({ username, personalAccessToken }),
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Supply an access token {#4-supply-an-access-token}
If you already hold a Docker access-token JWT, pass it as the client's access token. The SDK sends it as a bearer credential. A raw PAT is not an access-token JWT.
A fixed token has no refresh credential. Once it expires, create a client with a fresh token, or use the provider option below. The example accepts a service URL for applications that need an override; the default is `https://connect.docker.com/sandboxes`.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return new Sandboxes({
baseUrl: endpoint,
auth: bearer(accessToken),
});
```
<details>
<summary>Complete TypeScript example: cloudauth/connect.ts</summary>
```typescript
import { bearer, Sandboxes } from '@docker/sandboxes';
export function connectToCloud(endpoint: string, accessToken: string) {
return new Sandboxes({
baseUrl: endpoint,
auth: bearer(accessToken),
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Supply a token provider {#5-supply-a-token-provider}
Pass a callback that obtains a current Docker access token from your credential system. The callback returns the token string; your application owns its acquisition, storage, and renewal. Do not return an expired token.
Use one credential source per client. The SDK does not read environment variables or credentials saved by Docker command-line tools automatically.
Authentication errors mean the credential is missing, rejected, or expired. Sign in again or replace the credential. A permission error means the account cannot perform the requested action; check its Cloud Sandboxes access and resource permissions before retrying.
When you run commands or transfer files through a sandbox handle, the SDK obtains a credential scoped to that sandbox. You do not need to copy your account token into a second client.
The client reuses valid sandbox credentials. Concurrent requests share acquisition only within the same management client and for the same resolved Docker credential, sandbox endpoint and permissions. A later request renews the credential shortly before expiry. Renewal happens when needed, not on a five-minute timer, and does not change a request or stream already in progress.
Docker sign-in is separate from an agent's provider credential. To let an agent call its model provider, follow [Use secrets in a sandbox](get-a-stored-secret-into-a-sandbox.md).
Next, [run a complete program](run-a-complete-example.md) that signs in and launches a kit.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return new Sandboxes({
auth: { getAccessToken: tokenProvider },
});
```
<details>
<summary>Complete TypeScript example: cloudauth/provider.ts</summary>
```typescript
import { Sandboxes, type Authenticator } from '@docker/sandboxes';
export function connectWithTokenProvider(
tokenProvider: Authenticator['getAccessToken'],
) {
return new Sandboxes({
auth: { getAccessToken: tokenProvider },
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,122 @@
---
title: "Control what a sandbox can reach"
linkTitle: "Control what a sandbox can reach"
description: "Attach your account's network policies to a new sandbox, read the policy the service enforces, and review which destinations it allowed or blocked."
keywords: "cloud sandboxes, sandboxes api, control what a sandbox can reach"
weight: 401
params:
sidebar:
group: "Security and policy"
---
Control which destinations an agent or command can reach. A sandbox's effective policy combines account rules with the policies attached to it.
You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), a managed image, and policy IDs from your account. Create policies in the Console or with [Manage network policies](manage-network-policies.md).
## Attach policies at creation {#1-attach-policies-at-creation}
Pass the policy IDs when creating the sandbox. The example waits until the sandbox is running and returns its handle.
Attachments refer to existing policies; they do not contain policy definitions. Keep the IDs distinct from display names. Attached policies restrict access alongside account policy, so they cannot grant access that your account denies.
Include every destination the workload needs, including its model provider and package registries. A kit's network requirements do not override account restrictions.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.create(
{ displayName: name, image, network: { policyIds } },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: netpolicy/reference.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createWithPolicyIds(
client: Sandboxes,
name: string,
image: string,
policyIds: string[],
requestId: string,
) {
const sandbox = await client.create(
{ displayName: name, image, network: { policyIds } },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Inspect the rules in force {#2-inspect-the-rules-in-force}
Read the sandbox's effective policy before troubleshooting an application timeout. The result includes combined allow and deny rules, their origins, and an exact policy representation.
The effective view is the useful starting point for questions such as “can this sandbox reach api.anthropic.com?” Check the default mode as well as matching rules. A deny-by-default policy needs an explicit allowance for the destination.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.networkPolicies.get();
```
<details>
<summary>Complete TypeScript example: netpolicy/effective.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function getNetworkPolicies(sandbox: Sandbox) {
return sandbox.networkPolicies.get();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Check allowed and blocked traffic {#3-check-allowed-and-blocked-traffic}
List policy log entries for the sandbox, using `domain=network` to select network decisions. The example follows pagination and returns all matching entries.
Use each entry's destination, decision, reason, and count to identify blocked dependencies. An empty result means no matching entries were returned; it does not prove that a destination is reachable. The log records recent activity, not a permanent audit history.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.networkPolicies.logs.all({ filter }).collect();
```
<details>
<summary>Complete TypeScript example: netpolicy/logs.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function listPolicyLogEntries(
client: Sandboxes,
sandboxName: string,
filter: string,
) {
const sandbox = await client.get(sandboxName);
return sandbox.networkPolicies.logs.all({ filter }).collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,116 @@
---
title: "Copy a file into a cloud sandbox"
linkTitle: "Copy a file into a cloud sandbox"
description: "Create a directory inside a running cloud sandbox, write one file into it from memory, and read the size the write reports back."
keywords: "cloud sandboxes, sandboxes api, copy a file into a cloud sandbox"
weight: 202
params:
sidebar:
group: "Working in a sandbox"
---
Send project files, scripts, or input data to a running sandbox. Use its file collection instead of putting large file contents into command arguments.
You need a sandbox handle from [your first sandbox](create-your-first-sandbox.md) and an absolute destination path. The paths in these examples are inside the sandbox, not on your computer.
## Create the destination directory {#1-create-the-destination-directory}
Create the destination directory with the desired permission mode. The example also creates missing parent directories.
Choose the narrowest permissions the workload needs. Remember that a file's contents and its executable permission are separate settings.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await sandbox.files.mkdir(path, { mode, parents: true });
```
<details>
<summary>Complete TypeScript example: upload/mkdir.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function makeDirectory(
sandbox: Sandbox,
path: string,
mode: number,
) {
await sandbox.files.mkdir(path, { mode, parents: true });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Write a small text file {#2-write-a-small-text-file}
Use the write helper for a configuration file or short script already held as a string. Pass its destination path and content. For binary data or streamed transfers, use upload instead.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.files.write(path, content);
```
<details>
<summary>Complete TypeScript example: upload/write.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function writeFile(
sandbox: Sandbox,
path: string,
content: string,
) {
return sandbox.files.write(path, content);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Upload file content {#3-upload-file-content}
Upload bytes to the destination and read its metadata afterward. The example accepts content from your application; read a local file first if that is your source.
Choose a file mode explicitly when executable or restricted permissions matter. A failed upload may have written some data. Inspect the destination before retrying if replacing its contents would be unsafe.
Next, [run a command](run-your-first-command.md) that reads the file, or [read it back](read-files-out-of-a-sandbox.md) to verify the content.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await sandbox.files.upload(path, content, { mode });
return (await sandbox.files.stat(path)).info;
```
<details>
<summary>Complete TypeScript example: upload/upload.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function uploadFile(
sandbox: Sandbox,
path: string,
content: Uint8Array,
mode: number,
) {
await sandbox.files.upload(path, content, { mode });
return (await sandbox.files.stat(path)).info;
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,160 @@
---
title: "Create your first sandbox"
linkTitle: "Create your first sandbox"
description: "Launch a kit, run a command in its sandbox, and inspect the result."
keywords: "cloud sandboxes, sandboxes api, create your first sandbox"
weight: 103
params:
sidebar:
group: "Get started"
---
Create a sandbox from a kit and run a command inside it. A kit bundles an image and configuration for a tool or agent. The `shell` kit is a useful first choice because a greeting command needs no model-provider credential.
[Install an SDK](https://docs.docker.com/ai/sandboxes-api/install/) and [authenticate to Docker](connect-to-cloud-with-a-bearer-token.md) first. Pass the authenticated client to this example along with a kit name and command.
## Launch a kit and run a command {#1-launch-a-kit-and-run-a-command}
Choose `shell` as the kit name and pass `['echo', 'Hello from Docker Sandboxes']` as the command. The example requests 2 CPUs and 4096 MiB of memory. See [compute sizes and account limits](work-within-the-limits.md) when sizing other workloads.
The example launches the kit, waits until the sandbox is running, and runs the command through the sandbox's process collection. The SDK handles the connection and its sandbox-scoped credential.
A successful greeting returns `Hello from Docker Sandboxes` in `stdout` and exit code zero. See [Run your first command](run-your-first-command.md) for argument arrays, shell syntax, and interpreting command results.
Keep the returned sandbox name. This example leaves the sandbox available for the next guides. [Delete it](delete-a-cloud-sandbox.md) when you finish; closing the SDK client does not delete it. If a wait times out after creation was accepted, the sandbox can still exist.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const created = await client.kits.launch(
kitName,
{ resources: { cpus: 2, memoryMib: 4096 } },
{ timeoutMs, signal },
);
const sandbox = await created.waitUntilRunning({ timeoutMs, signal });
const result = await sandbox.processes.run(
{ args },
{ timeoutMs, signal },
);
return { sandbox, result };
```
<details>
<summary>Complete TypeScript example: quickstart/run.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createAndRun(
client: Sandboxes,
kitName: string,
args: string[],
timeoutMs = 300_000,
) {
const signal = AbortSignal.timeout(timeoutMs);
const created = await client.kits.launch(
kitName,
{ resources: { cpus: 2, memoryMib: 4096 } },
{ timeoutMs, signal },
);
const sandbox = await created.waitUntilRunning({ timeoutMs, signal });
const result = await sandbox.processes.run(
{ args },
{ timeoutMs, signal },
);
return { sandbox, result };
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Clean up a temporary sandbox automatically {#2-clean-up-a-temporary-sandbox-automatically}
For a single task, the scoped sandbox helper creates a sandbox, runs your callback, and attempts deletion afterward, including when the callback fails. This example explicitly stops the sandbox and waits for that transition before the helper attempts deletion. Supply the command arguments and creation options appropriate for the task; for a published image, set its reference and resources.
Cleanup has its own deadline. If it fails, inspect the workflow error and the retained sandbox identity so you can finish cleanup. Do not treat a client timeout as confirmation that the sandbox was deleted.
Next, [choose an agent kit](add-tools-with-kits.md) or [work with processes](run-your-first-command.md).
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.withSandbox(options, async (sandbox) => {
const outcome = await sandbox.processes
.run({ args }, { timeoutMs: 300_000 })
.then(
(value) => ({ value }),
(error: unknown) => ({ error }),
);
try {
const cleanup = { signal: AbortSignal.timeout(30_000) };
const current = await client.get(sandbox.name, cleanup);
if (current.uid !== sandbox.uid)
throw new Error(
'Sandbox identity changed; refusing to stop a replacement',
);
const stopped = await current.stop(cleanup);
await stopped.waitUntilStopped(cleanup);
} catch (error) {
if ('error' in outcome)
throw new AggregateError(
[outcome.error, error],
'Command and stop both failed',
);
throw error;
}
if ('error' in outcome) throw outcome.error;
return outcome.value;
});
```
<details>
<summary>Complete TypeScript example: quickstart/scoped.ts</summary>
```typescript
import type { ClientCreateOptions, Sandboxes } from '@docker/sandboxes';
export async function runTemporary(
client: Sandboxes,
options: ClientCreateOptions,
args: string[],
) {
return client.withSandbox(options, async (sandbox) => {
const outcome = await sandbox.processes
.run({ args }, { timeoutMs: 300_000 })
.then(
(value) => ({ value }),
(error: unknown) => ({ error }),
);
try {
const cleanup = { signal: AbortSignal.timeout(30_000) };
const current = await client.get(sandbox.name, cleanup);
if (current.uid !== sandbox.uid)
throw new Error(
'Sandbox identity changed; refusing to stop a replacement',
);
const stopped = await current.stop(cleanup);
await stopped.waitUntilStopped(cleanup);
} catch (error) {
if ('error' in outcome)
throw new AggregateError(
[outcome.error, error],
'Command and stop both failed',
);
throw error;
}
if ('error' in outcome) throw outcome.error;
return outcome.value;
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,64 @@
---
title: "Delete a cloud sandbox"
linkTitle: "Delete a cloud sandbox"
description: "Delete a sandbox you have read back and wait until it is gone, with the option to override a refusal based on its current state."
keywords: "cloud sandboxes, sandboxes api, delete a cloud sandbox"
weight: 106
params:
sidebar:
group: "Get started"
---
Delete a sandbox when its work is finished. Deletion removes its processes and sandbox-local files, so download results or save a snapshot first.
Use a current sandbox handle from creation or from reading its resource name. Closing the SDK client does not delete the sandbox.
## Delete and wait {#1-delete-and-wait}
Delete through the sandbox handle, then wait until the sandbox is absent. Deletion can terminate running work. If you choose to [stop the sandbox](stop-and-restart-a-sandbox.md) first, wait for that transition to finish before deleting it: deletion during a stop or resume can be refused.
The example takes an idempotency key and a wait deadline. Its optional force setting requests deletion when the service would otherwise refuse the sandbox's state. It does not guarantee that every state can be deleted immediately; handle a refusal rather than assuming cleanup succeeded.
The handle supplies the resource version it read. When an operation returns an updated handle, keep that handle for your next operation; older handles do not update themselves.
If deletion fails because the resource version changed, call `refresh()`. Inspect the returned handle and decide whether deletion is still appropriate. Refreshing does not change the original handle, so call delete on the returned one. If you supply idempotency keys, use a new key: deleting the refreshed version is a new logical operation, not a retry of the refused request.
If the wait times out, read the sandbox again to check its state. Keep its name so you can retry cleanup if needed. Repeating a completed deletion is safe.
Separate resources such as volumes, snapshots, and stored secrets have their own lifetimes. Delete those only when nothing still needs them.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const deleting = await sandbox.delete(
{ force },
{ idempotencyKey: requestId },
);
if (deleting) await deleting.waitUntilDeleted({ timeoutMs });
```
<details>
<summary>Complete TypeScript example: removal/delete.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function deleteSandbox(
sandbox: Sandbox,
requestId: string,
force = false,
timeoutMs = 300_000,
) {
const deleting = await sandbox.delete(
{ force },
{ idempotencyKey: requestId },
);
if (deleting) await deleting.waitUntilDeleted({ timeoutMs });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,111 @@
---
title: "Expose a port from a cloud sandbox"
linkTitle: "Expose a port from a cloud sandbox"
description: "Publish a TCP port that a program inside a cloud sandbox listens on, read back the URL that reaches it, list the sandbox's published ports, and withdraw the publication."
keywords: "cloud sandboxes, sandboxes api, expose a port from a cloud sandbox"
weight: 204
params:
sidebar:
group: "Working in a sandbox"
---
Reach a web application running inside a sandbox through an HTTPS URL. Publishing creates a route to the application; it does not start the application.
You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), the sandbox name, and an HTTP application listening on the port you will publish. Bind the application to an address reachable inside the sandbox, such as `0.0.0.0`.
## Publish the application's port {#1-publish-the-application-s-port}
Supply the sandbox name and the application's port number. The example uses TCP. Use the URL returned by the service rather than constructing a hostname.
This is HTTP application access, not a general-purpose TCP tunnel. Docker account authentication does not protect the application URL. Configure authentication in your application before exposing sensitive data, and never send your Docker account token to that URL.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.ports.create(
{ number, protocol: 'tcp' },
{ idempotencyKey: requestId },
);
```
<details>
<summary>Complete TypeScript example: ports/publish.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createPort(
client: Sandboxes,
sandboxName: string,
number: number,
requestId: string,
) {
const sandbox = await client.get(sandboxName);
return sandbox.ports.create(
{ number, protocol: 'tcp' },
{ idempotencyKey: requestId },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## List published ports {#2-list-published-ports}
Read the sandbox's port collection to find existing publications and their URLs. Check the application's readiness separately: a published route does not prove the application has started.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return (await sandbox.ports.list()).published ?? [];
```
<details>
<summary>Complete TypeScript example: ports/list.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function listPorts(sandbox: Sandbox) {
return (await sandbox.ports.list()).published ?? [];
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Withdraw a publication {#3-withdraw-a-publication}
Delete the published port when access is no longer needed. This removes the route but leaves the application process running.
Stopping the sandbox does not remove its port publications. Deleting the sandbox does.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await port.delete();
```
<details>
<summary>Complete TypeScript example: ports/unpublish.ts</summary>
```typescript
import type { Port } from '@docker/sandboxes';
export async function deletePort(port: Port) {
await port.delete();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,113 @@
---
title: "Find a process you lost track of"
linkTitle: "Find a process you lost track of"
description: "Look up a running process by the session tag you gave it when you created it, then pick up its output from the resume point its own report publishes."
keywords: "cloud sandboxes, sandboxes api, find a process you lost track of"
weight: 701
params:
sidebar:
group: "Long-running work"
---
Reconnect to work after losing a process connection. Starting a new command can duplicate effects, so first look for the process you already started.
Connections opened through TypeScript process handles recover from temporary disconnects while you consume output. They reconnect to the same process and resume after the last delivered chunk, with a 30-second budget per recovery episode. They never restart the command or replay terminal input. Raw streams require explicit reconnection. Use this guide when automatic recovery stops, you close a connection, or your application restarts.
You need a current sandbox handle and a session tag that your application assigned when it started the process. A session tag is searchable metadata, not the process's resource name.
## Find the running session {#1-find-the-running-session}
List processes filtered by session and running state. The example follows pagination. If multiple processes match, choose the intended resource by its saved name or metadata rather than attaching to an arbitrary result.
An empty list means no running process matched. It does not prove that the original command never started: it may have exited.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.processes
.all({ filter: `session=${session},state=running` })
.collect();
```
<details>
<summary>Complete TypeScript example: reconnect/list.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function findSession(sandbox: Sandbox, session: string) {
return sandbox.processes
.all({ filter: `session=${session},state=running` })
.collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Reconnect to its output {#2-reconnect-to-its-output}
Connect to the selected process handle and consume output events. The example resumes from the last sequence reported by that handle.
For delivery to another system, keep your own cursor for the last chunk that system actually handled. The latest sequence reported by the process may be ahead of that cursor. Use [explicit output resumption](run-an-interactive-shell-in-a-cloud-sandbox.md) when you need to replay from your saved position.
Close the connection when finished. Keep the process name if the connection drops again; do not replace reconnection with a second process start.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const connection = await process.connect({
resumeFrom: process.lastStreamSequence,
});
let lastSequence = BigInt(process.lastStreamSequence ?? '0');
try {
for await (const event of connection) {
if (event.type === 'chunk')
write(
event.data ?? new Uint8Array(),
(lastSequence = BigInt(event.streamSequence ?? '0')),
);
}
return lastSequence;
} finally {
await connection.close();
}
```
<details>
<summary>Complete TypeScript example: reconnect/attach.ts</summary>
```typescript
import type { Process } from '@docker/sandboxes';
export async function rejoinProcess(
process: Process,
write: (bytes: Uint8Array, sequence: bigint) => void,
) {
const connection = await process.connect({
resumeFrom: process.lastStreamSequence,
});
let lastSequence = BigInt(process.lastStreamSequence ?? '0');
try {
for await (const event of connection) {
if (event.type === 'chunk')
write(
event.data ?? new Uint8Array(),
(lastSequence = BigInt(event.streamSequence ?? '0')),
);
}
return lastSequence;
} finally {
await connection.close();
}
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,100 @@
---
title: "Get a stored secret into a sandbox"
linkTitle: "Get a stored secret into a sandbox"
description: "Store a credential once, then pass its resource name when creating a sandbox so the sandbox starts with that secret attached."
keywords: "cloud sandboxes, sandboxes api, get a stored secret into a sandbox"
weight: 405
params:
sidebar:
group: "Security and policy"
---
Give an agent access to its provider without placing the real credential in its command arguments or source files. Store the credential once, then attach its secret name when creating each sandbox.
Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Obtain the provider credential from that provider and read it from your application's secret manager. For Claude Code, use an Anthropic API key and service type `anthropic`; the Docker login token is not an Anthropic key.
## Store the provider credential {#1-store-the-provider-credential}
Supply a display name, service type, token value, and an idempotency key. Save the returned secret name. The response contains metadata, never the stored token.
Choose the service type that matches the workload's provider. A token for one provider does not authenticate another agent.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.secrets.create(
{ displayName: name, serviceType, token: { value: token } },
{ idempotencyKey: requestId },
);
```
<details>
<summary>Complete TypeScript example: credinject/store.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function storeSecret(
client: Sandboxes,
name: string,
serviceType: string,
token: string,
requestId: string,
) {
return client.secrets.create(
{ displayName: name, serviceType, token: { value: token } },
{ idempotencyKey: requestId },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Attach the secret at creation {#2-attach-the-secret-at-creation}
Put the secret name in the sandbox's storage options. This example uses a managed image; the same storage options can be passed when [launching a named kit](add-tools-with-kits.md).
Attach the secret when creating the sandbox, before starting the agent. Passing a secret's display name instead of its resource name will not select it. Keep the real token on the client side of the storage step rather than duplicating it in environment variables.
The SDK returns a handle after the example waits for the sandbox to run. Use that handle to run the agent. To replace or delete the credential later, follow [Manage cloud secrets](manage-cloud-secrets.md).
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.create(
{ displayName: name, image, storage: { secrets: secretNames } },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: credinject/attach.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createWithSecrets(
client: Sandboxes,
name: string,
image: string,
secretNames: string[],
requestId: string,
) {
const sandbox = await client.create(
{ displayName: name, image, storage: { secrets: secretNames } },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,57 @@
---
title: "Get an SSH certificate"
linkTitle: "Get an SSH certificate"
description: "Have the service sign your SSH public key for one sandbox and return the host, port, username, and host keys your SSH client needs."
keywords: "cloud sandboxes, sandboxes api, get an ssh certificate"
weight: 506
params:
sidebar:
group: "Connect and configure"
---
Request a short-lived SSH certificate for access to a sandbox. Keep the private key on your machine; send only the public key to Docker.
You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), a sandbox name, and an SSH public key. Generate a key pair with your SSH tooling if you do not have one.
## Request the certificate {#1-request-the-certificate}
Pass the sandbox name and public key. Save the returned certificate using your SSH client's expected certificate-file format, alongside the matching private key.
The response supplies the connection details for the sandbox. Use them rather than guessing a hostname or port. The example returns those details; it does not start an SSH process.
A certificate expires. Request a new one when needed, and keep the private key and certificate out of your repository. For programmatic command execution without an SSH client, use [processes](run-your-first-command.md).
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.ssh.issueCertificate({
publicKey,
ttlMs: lifeSeconds * 1_000,
});
```
<details>
<summary>Complete TypeScript example: ssh/certificate.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function issueSSHCert(
client: Sandboxes,
sandboxName: string,
publicKey: string,
lifeSeconds: number,
) {
const sandbox = await client.get(sandboxName);
return sandbox.ssh.issueCertificate({
publicKey,
ttlMs: lifeSeconds * 1_000,
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,45 @@
---
title: "Get image pull URLs"
linkTitle: "Get image pull URLs"
description: "Get the manifest details and short-lived download URLs for a managed image so a registry tool can pull its contents."
keywords: "cloud sandboxes, sandboxes api, get image pull urls"
weight: 507
params:
sidebar:
group: "Connect and configure"
---
Obtain the registry references needed to pull a managed image with an OCI-compatible tool. This is useful when another part of your workflow needs the image content outside a sandbox.
You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and the image name returned by [image registration](register-and-manage-an-image.md).
## Get the pull information {#1-get-the-pull-information}
Read the image's pull specification. The result identifies its manifest digest and image references.
Pass those references to your OCI client. This example only retrieves the pull information; it does not download layers or authenticate a separate registry client. Treat any returned access information as sensitive, and do not publish it in logs.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const image = await client.images.get(name);
return image.getPullSpec();
```
<details>
<summary>Complete TypeScript example: pullspec/spec.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function getImagePullSpec(client: Sandboxes, name: string) {
const image = await client.images.get(name);
return image.getPullSpec();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,246 @@
---
title: "Give a sandbox an MCP gateway"
linkTitle: "Give a sandbox an MCP gateway"
description: "Configure MCP tools when launching a kit, then inspect, authorize, and manage the gateway."
keywords: "cloud sandboxes, sandboxes api, give a sandbox an mcp gateway"
weight: 403
params:
sidebar:
group: "Security and policy"
---
Give an agent access to external tools through MCP, the Model Context Protocol. A gateway presents several tool servers through one endpoint.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and server IDs available to your account. Choose servers from the [Docker MCP Catalog](https://docs.docker.com/ai/mcp-catalog-and-toolkit/catalog/) and use their catalog IDs, not display names invented by your application. Provider sign-in and permission to use each tool may be required.
## Configure MCP when launching a kit {#1-configure-mcp-when-launching-a-kit}
Supply the server IDs in the kit's MCP options when creating the sandbox. This makes gateway configuration available when the agent starts. Attach any model-provider secrets the agent needs separately.
Use this creation-time path for Cloud Sandboxes. Adding a gateway after creation can be rejected for a governed sandbox; recreating it with MCP configured avoids that limitation.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.kits.launch(
kitName,
{
resources: { cpus: 2, memoryMib: 4096 },
mcp: { servers, static: true },
storage: { secrets },
},
{ timeoutMs: 300_000 },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: mcp/create.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function launchMcpKit(
client: Sandboxes,
kitName: string,
servers: string[],
secrets: string[] = [],
) {
const sandbox = await client.kits.launch(
kitName,
{
resources: { cpus: 2, memoryMib: 4096 },
mcp: { servers, static: true },
storage: { secrets },
},
{ timeoutMs: 300_000 },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Alternatively, start a gateway after creation {#2-alternatively-start-a-gateway-after-creation}
For an existing sandbox that supports gateway creation, use its MCP collection and wait until the gateway is ready. The sandbox must already have the required Docker credential. If startup is refused, do not assume that retrying will add missing credentials or change its policy configuration.
Inspect the returned server list and skipped-server reasons. A ready gateway does not guarantee that every requested server was accepted.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const gateway = await sandbox.mcp.start({ servers, static: true });
return gateway.waitUntilReady({ timeoutMs: 120_000 });
```
<details>
<summary>Complete TypeScript example: mcp/start.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function startMcpGateway(
client: Sandboxes,
name: string,
servers: string[],
) {
const sandbox = await client.get(name.replace(/\/mcp-gateway$/, ''));
const gateway = await sandbox.mcp.start({ servers, static: true });
return gateway.waitUntilReady({ timeoutMs: 120_000 });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Read the gateway address {#3-read-the-gateway-address}
Get the gateway and use its returned URL only when it is ready. The example checks readiness before returning the URL.
Keep gateway credentials private. The gateway's address and the published URL of an application inside the sandbox are different endpoints.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const gateway = await sandbox.mcp.get();
if (gateway.state !== 'ready' || !gateway.url)
throw new Error('MCP gateway is not ready');
return gateway.url;
```
<details>
<summary>Complete TypeScript example: mcp/read.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function readGatewayUrl(client: Sandboxes, name: string) {
const sandbox = await client.get(name.replace(/\/mcp-gateway$/, ''));
const gateway = await sandbox.mcp.get();
if (gateway.state !== 'ready' || !gateway.url)
throw new Error('MCP gateway is not ready');
return gateway.url;
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Add a catalog server {#4-add-a-catalog-server}
Add a server to a ready, writable gateway. Adding a server already present does not add a second copy. A shared gateway attached by URL cannot be modified through this sandbox.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await sandbox.mcp.servers.add({ server });
```
<details>
<summary>Complete TypeScript example: mcp/add.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function addMcpGatewayServer(
client: Sandboxes,
name: string,
server: string,
) {
const sandbox = await client.get(name.replace(/\/mcp-gateway$/, ''));
await sandbox.mcp.servers.add({ server });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Complete a server's sign-in {#5-complete-a-server-s-sign-in}
Start authorization for a server that requires user sign-in. Present its authorization URL to the user, then read the authorization resource to check progress. The example performs the start and read calls; your application decides how to display and poll the flow.
Only an authorized result means the credential is ready. Keep the authorization identity so a later attempt is not mistaken for completion of an earlier one. Request reauthorization only when you intend a new sign-in.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.mcp.authorizations.authorize({
server: name,
forceReauth,
});
```
<details>
<summary>Complete TypeScript example: mcp/authorize.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function authorizeMcpServer(
client: Sandboxes,
name: string,
forceReauth: boolean,
) {
return client.mcp.authorizations.authorize({
server: name,
forceReauth,
});
}
export async function getMcpAuthorization(
client: Sandboxes,
name: string,
) {
return client.mcp.authorizations.get(name);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Stop the gateway {#6-stop-the-gateway}
Stop the gateway when the sandbox no longer needs its tools. This does not delete the sandbox. Detaching from a shared gateway does not remove that gateway for its other users.
Existing agent processes may still hold old connection settings. Plan their restart or reconfiguration when changing the gateway.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await sandbox.mcp.stop();
```
<details>
<summary>Complete TypeScript example: mcp/stop.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function stopMcpGateway(client: Sandboxes, name: string) {
const sandbox = await client.get(name.replace(/\/mcp-gateway$/, ''));
await sandbox.mcp.stop();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,106 @@
---
title: "Handle errors and degradation"
linkTitle: "Handle errors and degradation"
description: "Tell an API refusal, a failed sandbox transition, and a nonzero command exit apart, and read the code and details each one carries."
keywords: "cloud sandboxes, sandboxes api, handle errors and degradation"
weight: 602
params:
sidebar:
group: "Requests and responses"
---
Distinguish a rejected request from an accepted operation that later fails. This keeps retries from hiding invalid input or duplicating work.
The SDK exposes typed request and wait errors. A command's nonzero exit code is a third outcome: the SDK can successfully observe a command that failed.
## Classify the failure {#1-classify-the-failure}
Use the error type and structured code, not the text of its message. The example groups common request failures and retains their details for the caller.
- Invalid arguments need a corrected request.
- Authentication failures need a valid credential; permission failures need appropriate account access.
- A wrong-state error needs a state check before another attempt.
- A capacity or quota refusal may require waiting or reducing usage.
- A transient availability failure may be retryable if the operation can be repeated safely.
Wait errors retain the state or resource observed before the wait stopped. Inspect that information and read the resource again when necessary. A failed create, an interrupted wait, and an expired client deadline do not have the same cleanup outcome.
A process helper can fail while obtaining a sandbox credential or reading output after the process has started. The error retains the accepted process and its cause. That process may still be running: inspect it before deciding whether to retry the command. Inspect the cause chain for a typed credential-service rate-limit error, including any request identifier and retry delay supplied by the service.
Unknown errors remain unknown in the example. They may be connection failures before any response arrived. Do not assume that a write was never accepted.
Keep credentials and secret-bearing request bodies out of diagnostics. Record resource names, error codes, and request identifiers instead. See [safe retries](retry-without-creating-duplicates.md) before repeating a mutation.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
if (!(error instanceof RequestError)) {
return { kind: 'other', code: undefined, details: [] };
}
const { code, details } = error.raw;
const known = KINDS.get(code);
return { kind: known ?? 'other', code, details: details ?? [] };
```
<details>
<summary>Complete TypeScript example: failures/classify.ts</summary>
```typescript
import { RequestError, ResourceWaitError } from '@docker/sandboxes';
type Status = RequestError['raw'];
type Detail = NonNullable<Status['details']>[number];
export type FailureKind =
| 'invalid-request'
| 'not-served'
| 'wrong-state'
| 'exhausted'
| 'access-or-missing'
| 'unavailable'
| 'other';
export interface Failure {
kind: FailureKind;
code: string | undefined;
details: Detail[];
}
const KINDS = new Map<string, FailureKind>([
['invalidArgument', 'invalid-request'],
['unimplemented', 'not-served'],
['failedPrecondition', 'wrong-state'],
['resourceExhausted', 'exhausted'],
['unauthenticated', 'access-or-missing'],
['permissionDenied', 'access-or-missing'],
['notFound', 'access-or-missing'],
['unavailable', 'unavailable'],
]);
export function readFailure(error: unknown): Failure {
if (!(error instanceof RequestError)) {
return { kind: 'other', code: undefined, details: [] };
}
const { code, details } = error.raw;
const known = KINDS.get(code);
return { kind: known ?? 'other', code, details: details ?? [] };
}
export function readSandboxWait(
error: unknown,
): 'degraded' | 'failed' | 'other' {
if (error instanceof ResourceWaitError) {
if (error.kind === 'interrupted' && error.state === 'degraded')
return 'degraded';
if (error.kind === 'failure' && error.state === 'failed')
return 'failed';
}
return 'other';
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,114 @@
---
title: "Keep a cloud sandbox running"
linkTitle: "Keep a cloud sandbox running"
description: "Choose a sandbox's lifetime and what happens when it ends, then renew the lifetime before the deadline passes."
keywords: "cloud sandboxes, sandboxes api, keep a cloud sandbox running"
weight: 503
params:
sidebar:
group: "Connect and configure"
---
Set a sandbox lifetime to prevent abandoned work from running indefinitely, and choose what happens when it expires.
You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and a managed image. The same lifecycle options can be supplied when launching a kit.
## Set the lifetime at creation {#1-set-the-lifetime-at-creation}
Pass the lifetime in the units shown by your SDK and choose the expiry action. The example accepts seconds and converts to the SDK's duration representation.
Stopping preserves the sandbox for later use; deletion removes it. Check that your choice matches whether the application needs the sandbox's files after expiry. Account limits may restrict permitted durations and actions.
Accounts with always-on access can instead select the restart action. This preserves memory through automatic stop-and-resume cycles. It retains a concurrency reservation while stopped and is different from restarting in response to an incoming request.
The example waits for startup. Its wait deadline is separate from the sandbox lifetime: ending a client wait does not delete the sandbox.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.create(
{
displayName: name,
image,
lifecycle: {
timeoutMs: lifeSeconds * 1_000,
onTimeout: stopOnTimeout ? 'stop' : 'delete',
},
},
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: timeout/deadline.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createWithDeadline(
client: Sandboxes,
name: string,
image: string,
lifeSeconds: number,
stopOnTimeout: boolean,
requestId: string,
) {
const sandbox = await client.create(
{
displayName: name,
image,
lifecycle: {
timeoutMs: lifeSeconds * 1_000,
onTimeout: stopOnTimeout ? 'stop' : 'delete',
},
},
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Extend the remaining lifetime {#2-extend-the-remaining-lifetime}
Use `renewTimeout()` to extend the sandbox's remaining lifetime. Pass the desired duration from the time the service accepts the renewal, not an increment to add to the existing deadline. Renewal can only extend the lifetime; it cannot shorten it.
Renew a configured timeout before it expires. If the call fails, inspect the sandbox's current state instead of assuming that renewal took effect.
The service manages timeouts for automatically restarting sandboxes; do not renew those yourself.
For request-triggered restart after a stop, see [Resume on demand](let-a-stopped-sandbox-resume-on-demand.md).
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.renewTimeout({ timeoutMs: remainingSeconds * 1_000 });
```
<details>
<summary>Complete TypeScript example: timeout/renew.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function renewSandboxTimeout(
client: Sandboxes,
name: string,
remainingSeconds: number,
) {
const sandbox = await client.get(name);
return sandbox.renewTimeout({ timeoutMs: remainingSeconds * 1_000 });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,94 @@
---
title: "Let a stopped sandbox resume on demand"
linkTitle: "Let a stopped sandbox resume on demand"
description: "Choose at creation whether a stopped cloud sandbox starts itself when a request arrives at one of its published ports, then read back the setting in force."
keywords: "cloud sandboxes, sandboxes api, let a stopped sandbox resume on demand"
weight: 508
params:
sidebar:
group: "Connect and configure"
---
Let a request to a published application start a stopped sandbox. This can suit an application that should be available on demand without remaining active between uses.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and a managed image that starts the application. [Publish its port](expose-a-port-from-a-cloud-sandbox.md) before relying on requests to reach it.
## Configure resume on creation {#1-configure-resume-on-creation}
Set automatic resume in the sandbox's lifecycle options. Omit the value when you want the service default; explicitly set false to disable it.
The example waits for the initial startup. Automatic resume does not recover a deleted sandbox, and it does not replace application startup configuration.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.create(
{ displayName: name, image, lifecycle: { autoResume } },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: wake/configure.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createWithAutoResume(
client: Sandboxes,
name: string,
image: string,
autoResume: boolean | undefined,
requestId: string,
) {
const sandbox = await client.create(
{ displayName: name, image, lifecycle: { autoResume } },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Read the effective setting {#2-read-the-effective-setting}
Get the sandbox and inspect the effective automatic-resume value together with its state. This checks what the service recorded; it does not send a request to the application or prove that the application can start.
To verify the whole flow, stop the sandbox, request its published application URL, and confirm that the application becomes ready. Allow for startup time and keep the application's own authentication enabled.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.get(name);
return {
autoResume: sandbox.effectiveFeatures?.timeouts?.autoResume ?? false,
status: sandbox.status,
};
```
<details>
<summary>Complete TypeScript example: wake/verify.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function autoResumeInForce(client: Sandboxes, name: string) {
const sandbox = await client.get(name);
return {
autoResume: sandbox.effectiveFeatures?.timeouts?.autoResume ?? false,
status: sandbox.status,
};
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,153 @@
---
title: "Manage cloud secrets"
linkTitle: "Manage cloud secrets"
description: "Store a token as a stored secret, list your secrets' metadata, replace the token, and delete the secret."
keywords: "cloud sandboxes, sandboxes api, manage cloud secrets"
weight: 402
params:
sidebar:
group: "Security and policy"
---
Store workload credentials separately from application code. Secret reads return metadata only, so keep the original credential in your secret manager if you need it elsewhere.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). These examples store a token for a named service. For instructions on giving a kit access to that token, see [Get a stored secret into a sandbox](get-a-stored-secret-into-a-sandbox.md).
## Create a secret {#1-create-a-secret}
Pass a display name, service type, token value, and idempotency key. Save the returned resource name for attachments and future updates. Do not log the request or token.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.secrets.create(
{ displayName: name, serviceType, token: { value: token } },
{ idempotencyKey: requestId },
);
```
<details>
<summary>Complete TypeScript example: secrets/create.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createSecret(
client: Sandboxes,
name: string,
serviceType: string,
token: string,
requestId: string,
) {
return client.secrets.create(
{ displayName: name, serviceType, token: { value: token } },
{ idempotencyKey: requestId },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## List stored credentials {#2-list-stored-credentials}
List secret metadata to find a credential by name or label. The example follows each page. Listing does not reveal secret values.
Use the resource name, not the display name, when attaching a secret to a sandbox.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.secrets.all().collect();
```
<details>
<summary>Complete TypeScript example: secrets/list.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function listSecrets(client: Sandboxes) {
return client.secrets.all().collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Rotate the credential {#3-rotate-the-credential}
Read the existing secret, then update it through a handle with the replacement value and service type. The handle carries the version it read so an intervening change is detected.
The update replaces the record's writable content. Send the service type again instead of assuming omitted values are preserved. Keep the returned metadata for subsequent operations.
If another writer changed the secret, read it again and decide whether to apply your replacement. Do not silently overwrite another rotation.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return secret.update(
{ serviceType, token: { value: token } },
{ idempotencyKey: requestId },
);
```
<details>
<summary>Complete TypeScript example: secrets/update.ts</summary>
```typescript
import type { Secret } from '@docker/sandboxes';
export async function updateSecret(
secret: Secret,
serviceType: string,
token: string,
requestId: string,
) {
return secret.update(
{ serviceType, token: { value: token } },
{ idempotencyKey: requestId },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Delete a secret {#4-delete-a-secret}
Delete through the secret handle when no workload needs the credential. Deleting an already absent secret is safe for repeated cleanup.
Removing a stored secret is not a substitute for revoking a leaked credential with its provider. Revoke compromised credentials and replace them before starting new workloads.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await secret.delete();
```
<details>
<summary>Complete TypeScript example: secrets/delete.ts</summary>
```typescript
import type { Secret } from '@docker/sandboxes';
export async function deleteSecret(secret: Secret) {
await secret.delete();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,75 @@
---
title: "Manage network policies"
linkTitle: "Manage network policies"
description: "Create, read, update, and delete personal network policies through the SDK."
keywords: "cloud sandboxes, sandboxes api, manage network policies"
weight: 406
params:
sidebar:
group: "Security and policy"
---
Create reusable network policies in your Docker account, then attach their IDs when creating sandboxes. The SDK's governance collection uses the same Docker authentication as the sandbox client.
Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). These methods manage your personal policies, not organization-wide administration.
## Manage a policy's lifetime {#1-manage-a-policy-s-lifetime}
The example creates a policy, lists the available policies, reads the new one, and replaces its definition. It attempts to delete the temporary policy in cleanup, including when a later step fails. Check cleanup errors; a cancelled request can leave the policy behind.
Supply a policy definition and a replacement using your SDK's policy input type. Include the destinations your workload needs. For a deny-by-default policy, allow the agent's model provider and any required package registries explicitly.
Set `status: 'POLICY_STATUS_ACTIVE'` on both the policy and its replacement. Although the SDK type makes this field optional, the service rejects omitted or other values with `unimplemented`.
An update replaces the definition rather than appending rules. Read the existing policy before deciding what to retain. Personal-policy mutations are not automatically replayed, so inspect the current state after an uncertain result before trying again.
This is a disposable policy-management example: the returned policy ID has been deleted by the time the function finishes. To keep a policy for real workloads, remove that demonstration cleanup and store its ID with your application configuration. Follow [Control what a sandbox can reach](control-what-a-sandbox-can-reach.md) to attach it and inspect enforced rules.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const created = await client.governance.policies.create(policy);
try {
const listed = await client.governance.policies.list();
const current = await client.governance.policies.get(created.id);
const updated = await client.governance.policies.update(
created.id,
replacement,
);
return { listed, current, updated };
} finally {
await client.governance.policies.delete(created.id);
}
```
<details>
<summary>Complete TypeScript example: policies/manage.ts</summary>
```typescript
import type { GovernancePolicyInput, Sandboxes } from '@docker/sandboxes';
export async function managePolicy(
client: Sandboxes,
policy: GovernancePolicyInput,
replacement: GovernancePolicyInput,
) {
const created = await client.governance.policies.create(policy);
try {
const listed = await client.governance.policies.list();
const current = await client.governance.policies.get(created.id);
const updated = await client.governance.policies.update(
created.id,
replacement,
);
return { listed, current, updated };
} finally {
await client.governance.policies.delete(created.id);
}
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,76 @@
---
title: "Page through and filter lists"
linkTitle: "Page through and filter lists"
description: "Walk every page of a list with the SDK's iterators, and narrow a list on the server with a filter and an ordering."
keywords: "cloud sandboxes, sandboxes api, page through and filter lists"
weight: 604
params:
sidebar:
group: "Requests and responses"
---
List resources without losing results at page boundaries. SDK collection iterators request each page for you; collecting materializes the complete result in memory.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). For a large account, process iterator items as they arrive instead of collecting them all.
## Walk every sandbox {#1-walk-every-sandbox}
Set a page size and iterate the sandbox collection. The example collects the results for convenience. A page size controls each request, not the total number of returned resources.
When using one-page methods directly, pass the returned next-page token unchanged and keep the filter, order, and page size stable. Stop when no next token is returned, not when a page contains fewer items than requested.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.all({ pageSize }).collect();
```
<details>
<summary>Complete TypeScript example: pagination/walk.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function walkSandboxes(client: Sandboxes, pageSize: number) {
return client.all({ pageSize }).collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Filter and order the result {#2-filter-and-order-the-result}
Pass a filter and ordering supported by the collection. The example uses images; choose a filter such as `status=completed` to select ready images.
Filters are strings interpreted by the service. Use the field names and operators documented for that list method rather than a language object's property names. An invalid filter should be fixed, not silently removed and retried as an unfiltered list.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.images.all({ filter, orderBy }).collect();
```
<details>
<summary>Complete TypeScript example: pagination/filter.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function filterImages(
client: Sandboxes,
filter: string,
orderBy: string,
) {
return client.images.all({ filter, orderBy }).collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,212 @@
---
title: "Read files out of a sandbox"
linkTitle: "Read files out of a sandbox"
description: "List a directory in a running cloud sandbox, download files from it into memory, then move or remove paths inside it."
keywords: "cloud sandboxes, sandboxes api, read files out of a sandbox"
weight: 203
params:
sidebar:
group: "Working in a sandbox"
---
Read build results, inspect project files, or copy data out before deleting a sandbox. Start with a running sandbox handle and an absolute path inside it.
The examples return content to your application. To keep it on your machine, write the received bytes to a local file.
## List a directory {#1-list-a-directory}
Walk the directory through the file collection's iterator. It follows pagination and returns the entries. Listing gives metadata, not file content.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.files.all(path).collect();
```
<details>
<summary>Complete TypeScript example: download/list.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function listDirectory(sandbox: Sandbox, path: string) {
return sandbox.files.all(path).collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Read a small text file {#2-read-a-small-text-file}
Use the bounded read helper for text you want in memory. The example limits the read to one MiB. Choose a bound that fits your application's memory budget, or download a larger file as a stream.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.files.read(path, {
encoding: 'utf8',
maxBytes: 1024 * 1024,
});
```
<details>
<summary>Complete TypeScript example: download/read.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function readFile(sandbox: Sandbox, path: string) {
return sandbox.files.read(path, {
encoding: 'utf8',
maxBytes: 1024 * 1024,
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Inspect a path {#3-inspect-a-path}
Read metadata before deciding whether to download, move, or remove a path. A successful metadata read does not reserve the file: another process may change it afterward.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.files.stat(path);
```
<details>
<summary>Complete TypeScript example: download/stat.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function statFile(sandbox: Sandbox, path: string) {
return sandbox.files.stat(path);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Download a file as a stream {#4-download-a-file-as-a-stream}
Pass the sandbox path and a callback or writer that consumes bytes. The example closes the transfer when it finishes or fails.
Treat the download as complete only when it ends successfully. If it fails halfway through, discard or separately identify the partial local file before retrying.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const download = await sandbox.files.download(path);
try {
for await (const chunk of download) write(chunk);
} finally {
await download.close();
}
```
<details>
<summary>Complete TypeScript example: download/download.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function downloadFiles(
sandbox: Sandbox,
path: string,
write: (bytes: Uint8Array) => void,
) {
const download = await sandbox.files.download(path);
try {
for await (const chunk of download) write(chunk);
} finally {
await download.close();
}
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Move a path {#5-move-a-path}
Pass the current path and destination. This moves data inside the sandbox; it does not download anything to your computer.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await sandbox.files.move(from, to);
```
<details>
<summary>Complete TypeScript example: download/move.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function movePath(
sandbox: Sandbox,
from: string,
to: string,
) {
await sandbox.files.move(from, to);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Remove a path {#6-remove-a-path}
Remove a file, or enable recursive removal for a directory tree. Check the result for a failed path rather than assuming that every requested removal succeeded.
Recursive removal is destructive. Keep user-supplied paths constrained to the directory your application owns.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const result = await sandbox.files.remove(path, { recursive });
if (result.failedPath)
throw new Error(`Remove ${path} stopped at ${result.failedPath}`);
```
<details>
<summary>Complete TypeScript example: download/remove.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function removePath(
sandbox: Sandbox,
path: string,
recursive: boolean,
) {
const result = await sandbox.files.remove(path, { recursive });
if (result.failedPath)
throw new Error(`Remove ${path} stopped at ${result.failedPath}`);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,88 @@
---
title: "Recover when the endpoint moves"
linkTitle: "Recover when the endpoint moves"
description: "Read a sandbox again to learn its current endpoint, then run one command through a connection built for that endpoint."
keywords: "cloud sandboxes, sandboxes api, recover when the endpoint moves"
weight: 704
params:
sidebar:
group: "Long-running work"
---
Refresh a sandbox handle after a restart or connection change. A saved address can become stale even though the sandbox still has the same resource name.
Start with the handle your application already holds. Keep its name and UID; do not replace them with another sandbox's identity.
## Compare the current endpoint {#1-compare-the-current-endpoint}
Refresh the handle, then compare the old and new connection information. The SDK checks the resource identity while constructing the refreshed handle.
The example reports whether the endpoint changed and returns a refreshed handle. Use that handle for later process and file calls.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const current = await held.refresh();
const before = held.core.endpoint;
const after = current.core.endpoint;
return {
moved:
before?.uri !== after?.uri || before?.protocol !== after?.protocol,
current,
};
```
<details>
<summary>Complete TypeScript example: rebind/detect.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function endpointMoved(held: Sandbox) {
const current = await held.refresh();
const before = held.core.endpoint;
const after = current.core.endpoint;
return {
moved:
before?.uri !== after?.uri || before?.protocol !== after?.protocol,
current,
};
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Run through the refreshed handle {#2-run-through-the-refreshed-handle}
Refresh before opening a new process connection. The SDK obtains the appropriate sandbox credential for the current endpoint; your application does not need to pass its Docker account token there.
This example starts new work after refresh. It does not replay a command whose completion is unknown. If an earlier command may still be running, [find and reconnect to it](find-a-process-you-lost-track-of.md) first.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const current = await held.refresh();
return current.processes.run({ args }, { timeoutMs: 300_000 });
```
<details>
<summary>Complete TypeScript example: rebind/rebuild.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function runAndRebind(held: Sandbox, args: string[]) {
const current = await held.refresh();
return current.processes.run({ args }, { timeoutMs: 300_000 });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,148 @@
---
title: "Register and manage an image"
linkTitle: "Register and manage an image"
description: "Register an image with Cloud Sandboxes, get the target to push its content to, check when it is ready, list your images, and delete one you no longer need."
keywords: "cloud sandboxes, sandboxes api, register and manage an image"
weight: 301
params:
sidebar:
group: "Packaging and state"
---
Reuse an image across sandboxes by registering it with Docker Cloud Sandboxes. Registration creates an image record and a temporary upload destination. You must push the image content separately with an OCI-compatible registry client.
Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and a built image. If you already have an image in a registry, [run it directly](run-your-own-container-image.md) instead.
## Register an upload destination {#1-register-an-upload-destination}
Choose a display name, startup command, CPU and memory preferences, and an idempotency key. The returned image includes its resource name and a push target.
Use the target's registry reference and temporary credential to push your image. Keep that credential private and finish before it expires. Save the target from the initial response: later image reads do not issue a replacement upload credential.
This example registers the destination. It does not build or push image content.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.images.create(
{ displayName: name, fromImage: { resources }, startCmd },
{ idempotencyKey: requestId },
);
```
<details>
<summary>Complete TypeScript example: images/create.ts</summary>
```typescript
import type {
ClientImagesCreateOptions,
Sandboxes,
} from '@docker/sandboxes';
export async function createImage(
client: Sandboxes,
name: string,
startCmd: string[],
resources: Extract<
ClientImagesCreateOptions,
{ fromImage: unknown }
>['fromImage']['resources'],
requestId: string,
) {
return client.images.create(
{ displayName: name, fromImage: { resources }, startCmd },
{ idempotencyKey: requestId },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Check preparation status {#2-check-preparation-status}
After the push finishes, read the image by its resource name. A `COMPLETED` status means it is ready to use. `WAITING_FOR_PUSH` means content has not arrived, `PREPARING` means preparation is in progress, and `FAILED` includes failure details.
The example performs one read. If preparation is still in progress, repeat the read with a delay and deadline. Repeating creation would register another image rather than advance this one.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.images.get(name);
```
<details>
<summary>Complete TypeScript example: images/ready.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function getImage(client: Sandboxes, name: string) {
return client.images.get(name);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Find registered images {#3-find-registered-images}
List images with an optional filter, such as `status=completed`. The example follows all pages. List entries are summaries; get the image by name when you need its complete record.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.images.all({ filter }).collect();
```
<details>
<summary>Complete TypeScript example: images/list.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function listImages(client: Sandboxes, filter: string) {
return client.images.all({ filter }).collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Remove an image {#4-remove-an-image}
Read the image and delete through its handle. The SDK supplies its name and version to protect against deleting a newer record you have not read.
Keep images that future sandbox creations still depend on. Deleting an already absent image is safe for cleanup.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await image.delete();
```
<details>
<summary>Complete TypeScript example: images/delete.ts</summary>
```typescript
import type { Image } from '@docker/sandboxes';
export async function deleteImage(image: Image) {
await image.delete();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,127 @@
---
title: "Retry without creating duplicates"
linkTitle: "Retry without creating duplicates"
description: "Retry a sandbox or process create under one request ID so a failure you can't interpret never leaves two copies behind."
keywords: "cloud sandboxes, sandboxes api, retry without creating duplicates"
weight: 603
params:
sidebar:
group: "Requests and responses"
---
Retry a request without creating duplicate work. An idempotency key identifies one logical operation, such as creating the sandbox for a particular job.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Generate the key once, then persist it with the job if retries can happen in another program run.
## Retry the same create request {#1-retry-the-same-create-request}
Reuse the same key and payload on every attempt. The example retries a transient availability refusal and disables automatic SDK retries. Do not run your own retry loop and the SDK's together without accounting for their total attempts.
Use a deadline and a bounded attempt count. For production retry scheduling, add delay and jitter or use the SDK's configured retry behavior. Changing the payload while keeping the key is not an update; it conflicts with the original request.
An accepted creation returns the sandbox's resource data. Save its name and use the client's get method to obtain a handle. Read or wait on that resource to follow progress instead of repeating creation to make it advance.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.kits.launch(
'shell',
{ displayName, resources: { cpus: 2, memoryMib: 4096 } },
{
idempotencyKey: requestId,
timeoutMs: 300_000,
signal,
maxRetries: 0,
},
);
} catch (error) {
if (
!(error instanceof RequestError) ||
error.raw.code !== 'unavailable' ||
attempt + 1 === attempts
)
throw error;
}
}
```
<details>
<summary>Complete TypeScript example: idempotency/retry.ts</summary>
```typescript
import { RequestError, type Sandboxes } from '@docker/sandboxes';
export async function createWithRetry(
client: Sandboxes,
displayName: string,
requestId: string,
attempts: number,
) {
if (!Number.isInteger(attempts) || attempts < 1)
throw new RangeError('attempts must be at least 1');
const signal = AbortSignal.timeout(300_000);
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.kits.launch(
'shell',
{ displayName, resources: { cpus: 2, memoryMib: 4096 } },
{
idempotencyKey: requestId,
timeoutMs: 300_000,
signal,
maxRetries: 0,
},
);
} catch (error) {
if (
!(error instanceof RequestError) ||
error.raw.code !== 'unavailable' ||
attempt + 1 === attempts
)
throw error;
}
}
throw new Error('attempt count was validated');
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Start a process once {#2-start-a-process-once}
Start the process with its own idempotency key and keep the returned process name. After losing a response or connection, find that process before starting another one.
Keys do not make every operation safe to replay. Repeating process input, a signal, or a file write can have a second effect. Use [process reconnection](find-a-process-you-lost-track-of.md) for a lost stream and inspect files before repeating an uncertain write.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.processes.start({ args }, { idempotencyKey: requestId });
```
<details>
<summary>Complete TypeScript example: idempotency/unsafe.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function createProcessOnce(
sandbox: Sandbox,
args: string[],
requestId: string,
) {
return sandbox.processes.start({ args }, { idempotencyKey: requestId });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,101 @@
---
title: "Run a complete example"
linkTitle: "Run a complete example"
description: "Sign in, launch the shell kit, print a greeting, and clean up with one runnable program."
keywords: "cloud sandboxes, sandboxes api, run a complete example"
weight: 102
params:
sidebar:
group: "Get started"
---
Run a small program that signs in to Docker, creates a sandbox from the `shell` kit, prints a greeting, and deletes the sandbox. You do not need a model-provider key for this example.
[Install your SDK](https://docs.docker.com/ai/sandboxes-api/install/) first. Your Docker account must have Cloud Sandboxes access and billing set up. This program creates a real sandbox with 2 CPUs and 4096 MiB of memory; compute usage is subject to your Docker billing terms.
## Run the program {#1-run-the-program}
Expand the complete example and copy the whole program into a file. Save it as `example.mts`, then run `npx tsx example.mts`.
The program prints a verification URL and code when its first API request needs authentication. Open the URL, enter the code, and approve sign-in. Your terminal then shows the sandbox's name and `Hello from Docker Sandboxes`. The program checks the command's exit status and cleans up its sandbox before closing the client.
If authentication fails, check the steps in [Authenticate to Docker](connect-to-cloud-with-a-bearer-token.md). If creation is refused, check account access and [resource limits](work-within-the-limits.md). A timeout does not prove that an accepted sandbox was deleted; keep any sandbox name reported with a cleanup error so you can inspect it.
This example deletes its sandbox because it is a one-off demonstration. Next, [keep a sandbox for later work](create-your-first-sandbox.md) or [run an agent kit](add-tools-with-kits.md).
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const result = await sandbox.processes.run(
{
args: ['echo', 'Hello from Docker Sandboxes'],
},
operation,
);
console.log(result.stdout);
```
<details>
<summary>Complete TypeScript example: hello/main.ts</summary>
```typescript
import { pathToFileURL } from 'node:url';
import { oauth, Sandboxes, type Sandbox } from '@docker/sandboxes';
export async function main() {
const auth = oauth({
onVerification: ({ verificationUri, userCode }) => {
console.log(`Open ${verificationUri} and enter ${userCode}`);
},
});
const client = new Sandboxes({ auth });
let sandbox: Sandbox | undefined;
try {
const operation = { timeoutMs: 300_000 };
sandbox = await client.kits.launch(
'shell',
{
resources: { cpus: 2, memoryMib: 4096 },
},
operation,
);
console.log(`Sandbox: ${sandbox.name}`);
sandbox = await sandbox.waitUntilRunning(operation);
const result = await sandbox.processes.run(
{
args: ['echo', 'Hello from Docker Sandboxes'],
},
operation,
);
console.log(result.stdout);
if (result.exitCode !== 0)
throw new Error(`Command exited with status ${result.exitCode}`);
} finally {
try {
if (sandbox) {
const cleanup = { signal: AbortSignal.timeout(30_000) };
const deleting = await sandbox.delete({ force: true }, cleanup);
await deleting?.waitUntilDeleted(cleanup);
}
} finally {
await client.close();
}
}
}
if (
process.argv[1] &&
import.meta.url === pathToFileURL(process.argv[1]).href
) {
main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,220 @@
---
title: "Run an interactive shell in a cloud sandbox"
linkTitle: "Run an interactive shell in a cloud sandbox"
description: "Start a shell on a pseudo-terminal, send it input and read its output over one connection, connect again and pick up the output the service kept, and stop it."
keywords: "cloud sandboxes, sandboxes api, run an interactive shell in a cloud sandbox"
weight: 201
params:
sidebar:
group: "Working in a sandbox"
---
Run a process with a terminal when it expects interactive input. A terminal session differs from a captured command: you manage input, output, and connection lifetime.
Use a running sandbox handle from [your first sandbox](create-your-first-sandbox.md). Pass the command as an argument array, such as `['sh']`.
## Start a terminal process {#1-start-a-terminal-process}
Start the process with a pseudo-terminal and an initial terminal size. The returned process handle identifies this session. Save its resource name if you want to reconnect later.
Use one idempotency key for this start request. Reconnecting does not require starting another process.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.processes.start(
{ args, pty: { initialSize: { rows: 40, cols: 120 } } },
{ idempotencyKey: requestId },
);
```
<details>
<summary>Complete TypeScript example: shell/create.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function createProcess(
sandbox: Sandbox,
args: string[],
requestId: string,
) {
return sandbox.processes.start(
{ args, pty: { initialSize: { rows: 40, cols: 120 } } },
{ idempotencyKey: requestId },
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Send input and read output {#2-send-input-and-read-output}
Connect to the process, write input, and consume output events. This example sends a finite input buffer and closes standard input; a terminal application should keep input open until the user finishes.
Forward output chunks to your terminal or callback. An exit event reports the process exit code. A connection ending without that event does not establish that the command finished.
Close the connection when you stop consuming it. Closing a connection and terminating the process are separate actions.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const connection = await process.connect();
try {
await connection.write(stdin);
await connection.closeStdin();
for await (const event of connection) {
if (event.type === 'chunk')
write(
event.data ?? new Uint8Array(),
BigInt(event.streamSequence ?? '0'),
);
if (event.type === 'exited') return event.exitCode ?? 0;
}
throw new Error(
`Stream for ${process.name} ended before the process exited`,
);
} finally {
await connection.close();
}
```
<details>
<summary>Complete TypeScript example: shell/attach.ts</summary>
```typescript
import type { Process } from '@docker/sandboxes';
export async function attachToProcess(
process: Process,
stdin: Uint8Array,
write: (bytes: Uint8Array, sequence: bigint) => void,
) {
const connection = await process.connect();
try {
await connection.write(stdin);
await connection.closeStdin();
for await (const event of connection) {
if (event.type === 'chunk')
write(
event.data ?? new Uint8Array(),
BigInt(event.streamSequence ?? '0'),
);
if (event.type === 'exited') return event.exitCode ?? 0;
}
throw new Error(
`Stream for ${process.name} ended before the process exited`,
);
} finally {
await connection.close();
}
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Resume output after a disconnect {#3-resume-output-after-a-disconnect}
Record the sequence number after handling each output chunk. On reconnect, pass the last handled sequence number to resume after that point.
Connections opened through TypeScript process handles resume automatically after temporary disconnects while you consume output. They use the last chunk delivered to your application, not the last chunk your application saved elsewhere. Raw streams require explicit reconnection. Keep your own cursor if you need to resume after an application restart. Input is never replayed; if a write fails, check the process before sending that input again.
Retain the process name with the cursor. A cursor from one process cannot identify output from another. Persist the cursor only after your application has handled the corresponding output.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const connection = await process.connect({ resumeFrom });
let lastSequence = BigInt(resumeFrom);
try {
for await (const event of connection) {
if (event.type === 'chunk') {
write(
event.data ?? new Uint8Array(),
(lastSequence = BigInt(event.streamSequence ?? '0')),
);
}
}
return lastSequence;
} finally {
await connection.close();
}
```
<details>
<summary>Complete TypeScript example: shell/resume.ts</summary>
```typescript
import type { Process } from '@docker/sandboxes';
export async function resumeProcessOutput(
process: Process,
resumeFrom: bigint,
write: (bytes: Uint8Array, sequence: bigint) => void,
) {
const connection = await process.connect({ resumeFrom });
let lastSequence = BigInt(resumeFrom);
try {
for await (const event of connection) {
if (event.type === 'chunk') {
write(
event.data ?? new Uint8Array(),
(lastSequence = BigInt(event.streamSequence ?? '0')),
);
}
}
return lastSequence;
} finally {
await connection.close();
}
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Send a signal {#4-send-a-signal}
Get the process by name and send the signal your application intends. Use a graceful termination signal when the process should clean up its own files.
Signals operate on the process; they do not delete the sandbox. [Delete the sandbox](delete-a-cloud-sandbox.md) separately when all work is finished.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const process = await sandbox.processes.get(name);
await process.signal(signal);
```
<details>
<summary>Complete TypeScript example: shell/signal.ts</summary>
```typescript
import type { Sandbox, Process } from '@docker/sandboxes';
export async function signalProcess(
sandbox: Sandbox,
name: string,
signal: Parameters<Process['signal']>[0],
) {
const process = await sandbox.processes.get(name);
await process.signal(signal);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,111 @@
---
title: "Run something that produces real output"
linkTitle: "Run something that produces real output"
description: "Choose between a call that returns a command's captured output when it finishes and a process whose output you read while it runs."
keywords: "cloud sandboxes, sandboxes api, run something that produces real output"
weight: 210
params:
sidebar:
group: "Working in a sandbox"
---
Choose how your application receives command output. Captured output is convenient for a short command; streaming lets you display progress or process output without waiting for completion.
Start with a running sandbox handle from [your first sandbox](create-your-first-sandbox.md). Supply the command as an argument array.
## Collect the result {#1-collect-the-result}
Run the process and wait for its result. Inspect standard output, standard error, and the exit code. Use an application deadline so a command cannot hold your request open indefinitely.
This form accumulates output for you. Prefer streaming when output could be large or your user needs progress updates.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.processes.run({ args }, { timeoutMs: 300_000 });
```
<details>
<summary>Complete TypeScript example: longrun/choose.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function collectOutput(sandbox: Sandbox, args: string[]) {
return sandbox.processes.run({ args }, { timeoutMs: 300_000 });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Stream output as it arrives {#2-stream-output-as-it-arrives}
Start the process, connect to it, and consume output events. The example forwards each output chunk to a callback and returns the exit code from the exit event.
The callback owns what happens to each chunk: display it, append it to a file, or send it to a client. Do not log sensitive output indiscriminately.
Always close the connection. A stream that ends before an exit event is an incomplete observation, not proof of success. Keep the process name to [reconnect](find-a-process-you-lost-track-of.md) rather than immediately starting a duplicate command.
Connections opened through TypeScript process handles reconnect after temporary disconnects while you consume output. They resume after the last delivered chunk and stop if recovery exceeds 30 seconds, without replaying process input. These connections have no default lifetime limit once connected, but a timeout you supply still limits the whole session. Raw streams require explicit reconnection.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const process = await sandbox.processes.start(
{ args },
{ idempotencyKey: requestId },
);
const connection = await process.connect();
try {
for await (const event of connection) {
if (event.type === 'chunk') write(event.data ?? new Uint8Array());
if (event.type === 'exited') return event.exitCode ?? 0;
}
throw new Error(
`Output of ${process.name} ended before the command exited`,
);
} finally {
await connection.close();
}
```
<details>
<summary>Complete TypeScript example: longrun/stream.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function streamOutput(
sandbox: Sandbox,
args: string[],
requestId: string,
write: (bytes: Uint8Array) => void,
) {
const process = await sandbox.processes.start(
{ args },
{ idempotencyKey: requestId },
);
const connection = await process.connect();
try {
for await (const event of connection) {
if (event.type === 'chunk') write(event.data ?? new Uint8Array());
if (event.type === 'exited') return event.exitCode ?? 0;
}
throw new Error(
`Output of ${process.name} ended before the command exited`,
);
} finally {
await connection.close();
}
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,126 @@
---
title: "Run work across many sandboxes"
linkTitle: "Run work across many sandboxes"
description: "Run one command on every running sandbox you hold, a bounded number at a time, and read one answer per sandbox even when some of them fail."
keywords: "cloud sandboxes, sandboxes api, run work across many sandboxes"
weight: 703
params:
sidebar:
group: "Long-running work"
---
Run a command across several existing sandboxes while keeping concurrency bounded. This can collect diagnostics or apply the same task to a group of independent workspaces.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and an argument array. Only run the command in sandboxes your application owns for this task.
## Select running sandboxes {#1-select-running-sandboxes}
Walk the sandbox collection and retain running handles. The example selects all running sandboxes visible to the client; narrow that selection to your application's jobs before executing a command with side effects.
Listing and execution are separate requests. A sandbox can change state between them, so the execution step still needs to handle failures.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const running: Sandbox[] = [];
for await (const sandbox of client.all()) {
if (sandbox.status === 'running') running.push(sandbox);
}
return running;
```
<details>
<summary>Complete TypeScript example: fanout/spread.ts</summary>
```typescript
import type { Sandbox, Sandboxes } from '@docker/sandboxes';
export async function runningSandboxes(client: Sandboxes) {
const running: Sandbox[] = [];
for await (const sandbox of client.all()) {
if (sandbox.status === 'running') running.push(sandbox);
}
return running;
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Collect individual results {#2-collect-individual-results}
Choose a concurrency bound and run the command on each selected sandbox. The example keeps each result or failure alongside the sandbox name, so one failed request does not discard every other result.
Inspect both request failures and process exit codes. A returned process result can contain a nonzero exit code.
Keep concurrency below your account's limits and your application's memory budget. For large output, stream results instead of collecting them all. The example does not delete the existing sandboxes.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const outcomes: Outcome[] = [];
const size = Number.isFinite(bound) ? Math.max(1, Math.trunc(bound)) : 1;
for (let start = 0; start < sandboxes.length; start += size) {
const group = sandboxes.slice(start, start + size);
const settled = await Promise.allSettled(
group.map((sandbox) =>
sandbox.processes.run({ args }, { timeoutMs: 300_000 }),
),
);
settled.forEach((result, index) => {
outcomes.push(
result.status === 'fulfilled'
? { sandboxName: group[index].name, response: result.value }
: { sandboxName: group[index].name, failure: result.reason },
);
});
}
return outcomes;
```
<details>
<summary>Complete TypeScript example: fanout/gather.ts</summary>
```typescript
import type { RunResult, Sandbox } from '@docker/sandboxes';
export type Outcome = {
sandboxName: string;
response?: RunResult;
failure?: unknown;
};
export async function runOnEach(
sandboxes: Sandbox[],
args: string[],
bound: number,
) {
const outcomes: Outcome[] = [];
const size = Number.isFinite(bound) ? Math.max(1, Math.trunc(bound)) : 1;
for (let start = 0; start < sandboxes.length; start += size) {
const group = sandboxes.slice(start, start + size);
const settled = await Promise.allSettled(
group.map((sandbox) =>
sandbox.processes.run({ args }, { timeoutMs: 300_000 }),
),
);
settled.forEach((result, index) => {
outcomes.push(
result.status === 'fulfilled'
? { sandboxName: group[index].name, response: result.value }
: { sandboxName: group[index].name, failure: result.reason },
);
});
}
return outcomes;
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,112 @@
---
title: "Run your first command"
linkTitle: "Run your first command"
description: "Connect to a running sandbox, run a command in it, and tell a command that exited unsuccessfully apart from a call that failed."
keywords: "cloud sandboxes, sandboxes api, run your first command"
weight: 105
params:
sidebar:
group: "Get started"
---
Run a command in an existing sandbox and collect its output. Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and the sandbox name returned by [your first launch](create-your-first-sandbox.md).
For most commands, use the process collection's run helper. It starts the process and waits for its result.
## Open the sandbox {#1-open-the-sandbox}
Get the sandbox by its resource name. The returned handle exposes processes and files and manages their authenticated connections. You do not need to copy the sandbox address or Docker token into a second client.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.get(name);
```
<details>
<summary>Complete TypeScript example: exec/endpoint.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function processesAt(client: Sandboxes, name: string) {
return client.get(name);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Use the process run helper {#2-use-the-process-run-helper}
Pass an argument array: the program followed by its arguments, such as `['echo', 'Hello from Docker Sandboxes']`. Arguments are not shell syntax. For pipes, redirection, or variable expansion, run `sh -c` with your script as the next argument.
The run helper returns the process result after collecting output. A successful SDK call does not mean the command succeeded: inspect its exit code. Standard error is diagnostic output and can be nonempty even when the exit code is zero.
An SDK error means the request, wait, or connection failed. Keep the process handle carried by an error when available so you can inspect it instead of starting duplicate work. [Error handling](handle-errors-and-degradation.md) explains the distinction.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return sandbox.processes.run({ args }, { timeoutMs: 300_000 });
```
<details>
<summary>Complete TypeScript example: exec/outcome.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function classifyOutcome(sandbox: Sandbox, args: string[]) {
return sandbox.processes.run({ args }, { timeoutMs: 300_000 });
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Optional: execute a command directly {#3-optional-execute-a-command-directly}
For a short command whose output you only need after it exits, direct execution is simpler: one request waits for the command and returns its captured output and exit code, without a separate process-creation and output-reading sequence.
Check the incomplete flag before treating captured output as complete. Choose [streaming output](run-something-that-produces-real-output.md) when you need output while the command runs.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const result = await endpoint.api.exec({ body: { cmd: args } });
```
<details>
<summary>Complete TypeScript example: exec/exec.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function runCommand(sandbox: Sandbox, args: string[]) {
const endpoint = await sandbox.endpointClient(['sandboxesExec'], 'exec');
try {
const result = await endpoint.api.exec({ body: { cmd: args } });
return {
exitCode: result.exitCode ?? 0,
stdout: new TextDecoder().decode(result.stdout),
stderr: new TextDecoder().decode(result.stderr),
incomplete: result.incomplete ?? false,
};
} finally {
await endpoint.close();
}
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,61 @@
---
title: "Run your own container image"
linkTitle: "Run your own container image"
description: "Start a sandbox from a container image in a registry you name, instead of from a managed image."
keywords: "cloud sandboxes, sandboxes api, run your own container image"
weight: 501
params:
sidebar:
group: "Connect and configure"
---
Run your own tools from a container image when a [bundled kit](add-tools-with-kits.md) does not fit the task. You supply the image reference and machine size; the SDK creates an isolated sandbox around it.
You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and an OCI image the service can pull. Use a versioned reference or digest when repeatability matters. The image must include `/bin/sh`.
## Create from a registry image {#1-create-from-a-registry-image}
Pass the image reference, CPU count, memory size, display name, and idempotency key. The SDK's image-reference option avoids assembling nested request fields. Do not also supply a managed image or named agent.
The example waits for the sandbox to run. A wait timeout stops your wait; it does not delete a sandbox that was already accepted. Save any sandbox handle retained by the error so you can inspect or clean it up.
The image's startup command runs inside the sandbox. Read `WORKSPACE_DIR` to find the workspace rather than assuming a path.
Next, [run a command](run-your-first-command.md) or [copy in your project files](copy-a-file-into-a-cloud-sandbox.md). [Delete the sandbox](delete-a-cloud-sandbox.md) when the work is complete.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await client.create(
{ displayName: name, imageRef, resources },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: rawimage/create.ts</summary>
```typescript
import type { ClientCreateOptions, Sandboxes } from '@docker/sandboxes';
export async function createFromImageRef(
client: Sandboxes,
name: string,
imageRef: string,
resources: ClientCreateOptions['resources'],
requestId: string,
) {
const sandbox = await client.create(
{ displayName: name, imageRef, resources },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,110 @@
---
title: "Send values the API accepts"
linkTitle: "Send values the API accepts"
description: "Build a create request whose durations, counts, and flags mean what you intend, then read back the values the service settled on."
keywords: "cloud sandboxes, sandboxes api, send values the api accepts"
weight: 605
params:
sidebar:
group: "Requests and responses"
---
Build SDK options without confusing an omitted value with an explicit false or zero. This matters for settings whose default is chosen by the service.
Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Use the option types exported by your SDK so your editor can show the supported inputs.
## Construct creation options {#1-construct-creation-options}
Supply the image source and lifecycle settings, using the duration units required by your SDK. The example accepts seconds and converts them at the boundary.
This example uses a managed image, which supplies its resource defaults. For a registry image instead, use the image-reference option and provide a supported CPU and memory pair.
Preserve absence for an optional boolean when you want the service default. Explicit false is a choice, not a missing value. Validate user-supplied values before building the request.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return {
displayName: name,
image,
platform,
lifecycle: { timeoutMs: lifeSeconds * 1_000, autoResume },
};
```
<details>
<summary>Complete TypeScript example: values/build.ts</summary>
```typescript
import type { ClientCreateOptions, Sandboxes } from '@docker/sandboxes';
export function createRequest(
name: string,
image: string,
lifeSeconds: number,
platform: ClientCreateOptions['platform'],
autoResume: boolean | undefined,
): ClientCreateOptions {
return {
displayName: name,
image,
platform,
lifecycle: { timeoutMs: lifeSeconds * 1_000, autoResume },
};
}
export async function sendCreate(
client: Sandboxes,
request: ClientCreateOptions,
) {
const sandbox = await client.create(request, { timeoutMs: 300_000 });
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Read the effective values {#2-read-the-effective-values}
Get the sandbox and inspect the reported platform, resources, and expiration. These describe what the service recorded, which can differ from omitted or defaulted input values.
Keep the language's native numeric and duration representations. Avoid converting large integers through a floating-point type merely to display or serialize them.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return {
expiresAt: sandbox.effectiveFeatures?.timeouts?.expiresAt,
platform: sandbox.core.platform,
cpus: sandbox.core.resources?.cpus ?? undefined,
};
```
<details>
<summary>Complete TypeScript example: values/read.ts</summary>
```typescript
import type { Sandbox, Sandboxes } from '@docker/sandboxes';
export function reportedValues(sandbox: Sandbox) {
return {
expiresAt: sandbox.effectiveFeatures?.timeouts?.expiresAt,
platform: sandbox.core.platform,
cpus: sandbox.core.resources?.cpus ?? undefined,
};
}
export async function readSandbox(client: Sandboxes, name: string) {
return reportedValues(await client.get(name));
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,172 @@
---
title: "Snapshot and fork a sandbox"
linkTitle: "Snapshot and fork a sandbox"
description: "Capture the state of a running sandbox as a snapshot, then start a new sandbox from that snapshot."
keywords: "cloud sandboxes, sandboxes api, snapshot and fork a sandbox"
weight: 302
params:
sidebar:
group: "Packaging and state"
---
Save a sandbox's state so you can start another sandbox from the same point later. A snapshot is separate from its source sandbox; restoring it creates a new sandbox with its own identity.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and the resource name of a running sandbox.
## Capture a snapshot {#1-capture-a-snapshot}
Choose a display name, capture mode, and idempotency key. Use disk-only capture when you need the filesystem. Request memory only when your environment supports it and you need the running state.
The example waits until capture finishes and returns a snapshot handle. Keep the returned name for restoration.
A failed capture carries failure details; a timed-out wait does not prove the capture was cancelled.
Snapshots can contain credentials and private project data. Restrict access to them, especially when capturing memory.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const snapshot = await sandbox.snapshot(
{
displayName: snapshotName,
captureMode: withMemory ? 'all' : 'disk',
},
{ idempotencyKey: requestId },
);
return snapshot.waitUntilReady();
```
<details>
<summary>Complete TypeScript example: snapshots/create.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function createSnapshot(
client: Sandboxes,
sandboxName: string,
snapshotName: string,
withMemory: boolean,
requestId: string,
) {
const sandbox = await client.get(sandboxName);
const snapshot = await sandbox.snapshot(
{
displayName: snapshotName,
captureMode: withMemory ? 'all' : 'disk',
},
{ idempotencyKey: requestId },
);
return snapshot.waitUntilReady();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Restore into another sandbox {#2-restore-into-another-sandbox}
Use a ready snapshot's name and a display name for the new sandbox. The example waits for the restored sandbox to run.
The call returns a sandbox handle, ready for running commands or transferring files.
The source sandbox does not need to remain running. Restoration does not replace it. Save the new sandbox's name and delete it separately when you finish.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const sandbox = await snapshot.restore(
{ displayName: newSandboxName },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: snapshots/restore.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function restoreSnapshot(
client: Sandboxes,
snapshotName: string,
newSandboxName: string,
requestId: string,
) {
const snapshot = await client.snapshots.get(snapshotName);
const sandbox = await snapshot.restore(
{ displayName: newSandboxName },
{ timeoutMs: 300_000, idempotencyKey: requestId },
);
return sandbox.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Find saved snapshots {#3-find-saved-snapshots}
List snapshots for the source sandbox. The example follows all pages and returns summaries. Read a selected snapshot before restoring it if you need its current status or complete metadata.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
return client.snapshots.all({ sandbox: sandboxName }).collect();
```
<details>
<summary>Complete TypeScript example: snapshots/list.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function listSnapshots(
client: Sandboxes,
sandboxName: string,
) {
return client.snapshots.all({ sandbox: sandboxName }).collect();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Delete a snapshot {#4-delete-a-snapshot}
Delete through a handle when you no longer need that restore point. Deletion removes the snapshot, not sandboxes that have already been restored from it. Snapshot deletion is refused while a sandbox restored from it is still running.
Deleting the source sandbox and deleting its snapshots are separate cleanup steps.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
await snapshot.delete();
```
<details>
<summary>Complete TypeScript example: snapshots/delete.ts</summary>
```typescript
import type { Snapshot } from '@docker/sandboxes';
export async function deleteSnapshot(snapshot: Snapshot) {
await snapshot.delete();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,80 @@
---
title: "Stop and restart a sandbox"
linkTitle: "Stop and restart a sandbox"
description: "Stop a running sandbox without deleting it, wait for the stop to settle, and start the same sandbox again when you need it."
keywords: "cloud sandboxes, sandboxes api, stop and restart a sandbox"
weight: 207
params:
sidebar:
group: "Working in a sandbox"
---
Stop a sandbox when you want to keep its disk but pause its execution. Start the same sandbox later to continue working with those files.
Use a sandbox handle obtained by creation or by reading its resource name. Stopping is different from [deleting](delete-a-cloud-sandbox.md), which removes the sandbox.
## Stop the sandbox {#1-stop-the-sandbox}
Call stop and wait for the stopped state. The example returns a handle with the updated resource version.
Wait completion confirms the state change. A timeout means your client stopped waiting; read the sandbox to learn whether the operation completed.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const changed = await sandbox.stop({ idempotencyKey: requestId });
return changed.waitUntilStopped();
```
<details>
<summary>Complete TypeScript example: lifecycle/stop.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function stopSandbox(sandbox: Sandbox, requestId: string) {
const changed = await sandbox.stop({ idempotencyKey: requestId });
return changed.waitUntilStopped();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Start it again {#2-start-it-again}
Do not reuse a handle whose version predates the stop.
Start the stopped sandbox and wait for it to run. As with stopping, the call returns a handle.
Do not assume that a process connection from before the stop remains usable. [Find an existing process](find-a-process-you-lost-track-of.md) or start the work again as appropriate for the application.
A stopped sandbox still exists. Delete it when you no longer need its disk, and clean up separate snapshots or volumes only when their data is no longer needed.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const changed = await sandbox.start({ idempotencyKey: requestId });
return changed.waitUntilRunning();
```
<details>
<summary>Complete TypeScript example: lifecycle/start.ts</summary>
```typescript
import type { Sandbox } from '@docker/sandboxes';
export async function startSandbox(sandbox: Sandbox, requestId: string) {
const changed = await sandbox.start({ idempotencyKey: requestId });
return changed.waitUntilRunning();
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,136 @@
---
title: "What your workload starts with"
linkTitle: "What your workload starts with"
description: "Read the environment variables every command in a sandbox starts with, and see which source wins when two of them set the same name."
keywords: "cloud sandboxes, sandboxes api, what your workload starts with"
weight: 209
params:
sidebar:
group: "Working in a sandbox"
---
Inspect the environment a process receives and override a value for one command. This helps diagnose a missing workspace path or configuration setting.
Use a running sandbox handle. The environment can contain credentials, so do not dump the full result into logs or send it to an untrusted client.
## Read the process environment {#1-read-the-process-environment}
Run `env` and parse its output. Read `WORKSPACE_DIR` to locate the workspace rather than assuming a fixed path.
This reports the environment seen by that process. It is a diagnostic example, not a way to retrieve stored secret values.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
export async function readFloor(sandbox: Sandbox) {
const result = requireSuccess(
await sandbox.processes.run({ args: ['env'] }, { timeoutMs: 300_000 }),
);
return Object.fromEntries(
result.stdout
.split('\n')
.filter((line) => line.includes('='))
.map((line) => {
const delimiter = line.indexOf('=');
return [line.slice(0, delimiter), line.slice(delimiter + 1)];
}),
);
}
```
<details>
<summary>Complete TypeScript example: runtime/read.ts</summary>
```typescript
import { requireSuccess, type Sandbox } from '@docker/sandboxes';
export async function readFloor(sandbox: Sandbox) {
const result = requireSuccess(
await sandbox.processes.run({ args: ['env'] }, { timeoutMs: 300_000 }),
);
return Object.fromEntries(
result.stdout
.split('\n')
.filter((line) => line.includes('='))
.map((line) => {
const delimiter = line.indexOf('=');
return [line.slice(0, delimiter), line.slice(delimiter + 1)];
}),
);
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Override a variable for one command {#2-override-a-variable-for-one-command}
Supply a value in the process request's environment map. The first command reads that override. A second command without the override reads the sandbox's original value.
The override belongs to the process request and does not change the sandbox's environment for later commands. Use it for task-specific configuration. For provider credentials, prefer [stored secrets](get-a-stored-secret-into-a-sandbox.md).
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
const fromRequest = requireSuccess(
await sandbox.processes.run(
{
args: ['printenv', name],
env: { [name]: value },
},
{ timeoutMs: 300_000 },
),
);
const fromSandbox = await sandbox.processes.run(
{
args: ['printenv', name],
},
{ timeoutMs: 300_000 },
);
return {
fromRequest: fromRequest.stdout.trimEnd(),
fromSandbox: fromSandbox.stdout.trimEnd(),
};
```
<details>
<summary>Complete TypeScript example: runtime/precedence.ts</summary>
```typescript
import { requireSuccess, type Sandbox } from '@docker/sandboxes';
export async function overrideVariable(
sandbox: Sandbox,
name: string,
value: string,
) {
const fromRequest = requireSuccess(
await sandbox.processes.run(
{
args: ['printenv', name],
env: { [name]: value },
},
{ timeoutMs: 300_000 },
),
);
const fromSandbox = await sandbox.processes.run(
{
args: ['printenv', name],
},
{ timeoutMs: 300_000 },
);
return {
fromRequest: fromRequest.stdout.trimEnd(),
fromSandbox: fromSandbox.stdout.trimEnd(),
};
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,178 @@
---
title: "Work within the limits"
linkTitle: "Work within the limits"
description: "Choose a supported compute size, understand account quotas, and handle refusals without endless retries."
keywords: "cloud sandboxes, sandboxes api, work within the limits"
weight: 606
params:
sidebar:
group: "Requests and responses"
---
Bound work so account limits and temporary capacity refusals do not turn into unbounded retries.
Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Account limits are separate from SDK configuration; changing the client does not grant additional capacity.
### Choose a compute size
Cloud Sandboxes uses fixed CPU and memory pairs as billing shapes:
| Size | vCPUs | Memory | SDK memory value (MiB) |
| ------ | ----: | -----: | ---------------------: |
| Micro | 1 | 2 GiB | 2048 |
| Small | 2 | 4 GiB | 4096 |
| Medium | 4 | 8 GiB | 8192 |
| Large | 8 | 16 GiB | 16384 |
| XL | 16 | 32 GiB | 32768 |
Choose a size by name, for example, `resources: 'small'`. The supported names are `micro`, `small`, `medium`, `large`, and `xl`. Kit launches default to Small when you omit resources. Explicit settings take precedence.
You can still supply both CPU and memory directly. Use a supported pair; 1 CPU with 1024 MiB is not supported in Cloud Sandboxes. A saved image supplies its own resources, so do not override them when creating from that image. Your account and available capacity determine whether a request can be accepted. Check your Docker billing terms for prices.
### Understand account quotas
The default limits are 10 concurrent sandboxes, 50 stored sandboxes, 100 volumes, 100 secrets, and 3 images being prepared at once. These are account-wide defaults, not a separate allowance per user. Your account can have different limits; confirm them with Docker before sizing a large workload.
Stopping an ordinary sandbox frees its concurrency slot but leaves it in stored usage. Starting or resuming it needs a slot again. An always-on sandbox, configured to restart automatically on timeout, retains its concurrency reservation even while stopped. Delete sandboxes you no longer need to release stored usage.
## Count existing sandboxes {#1-count-existing-sandboxes}
Walk all sandbox pages and count resources visible to you. This gives your application a usage observation, not a reservation or authoritative quota balance. Other users can hold resources in the same account that your list does not show.
Another caller can create a sandbox immediately afterward. Treat the create response as the final decision, even when your count appears below a planned limit.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
let count = 0;
for await (const sandbox of client.all({ pageSize })) {
if (sandbox.name) count++;
}
return count;
```
<details>
<summary>Complete TypeScript example: limits/budget.ts</summary>
```typescript
import type { Sandboxes } from '@docker/sandboxes';
export async function countSandboxes(client: Sandboxes, pageSize: number) {
let count = 0;
for await (const sandbox of client.all({ pageSize })) {
if (sandbox.name) count++;
}
return count;
}
```
</details>
{{< /tab >}}
{{< /tabs >}}
## Back off after a refusal {#2-back-off-after-a-refusal}
Use the server's retry delay when supplied and keep the same idempotency key for the same create request. Bound the number of attempts and give the overall operation a deadline.
The example owns the retry loop, disables automatic SDK retries, and sets an overall timeout. Do not layer two retry policies without accounting for their combined attempts and deadlines.
A quota refusal may need cleanup or an account change rather than another immediate request. Stop after the bound, report the failure, and keep any accepted sandbox identity available for inspection.
{{< tabs >}}
{{< tab name="TypeScript" >}}
```typescript
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.kits.launch(
'shell',
{ displayName, resources: { cpus: 2, memoryMib: 4096 } },
{
idempotencyKey: requestId,
timeoutMs: 300_000,
signal,
maxRetries: 0,
},
);
} catch (error) {
if (
!(error instanceof RequestError) ||
error.raw.code !== 'resourceExhausted' ||
attempt + 1 === attempts
)
throw error;
await pause(retryAfter(error, fallbackMs), undefined, { signal });
}
}
throw new Error('attempt count was validated');
```
<details>
<summary>Complete TypeScript example: limits/backoff.ts</summary>
```typescript
import { setTimeout as pause } from 'node:timers/promises';
import {
RequestError,
type Sandbox,
type Sandboxes,
} from '@docker/sandboxes';
export async function createWithBackoff(
client: Sandboxes,
displayName: string,
attempts: number,
fallbackMs: number,
requestId: string,
): Promise<Sandbox> {
if (!Number.isInteger(attempts) || attempts < 1)
throw new Error(`attempts must be at least 1, got ${attempts}`);
const signal = AbortSignal.timeout(300000);
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.kits.launch(
'shell',
{ displayName, resources: { cpus: 2, memoryMib: 4096 } },
{
idempotencyKey: requestId,
timeoutMs: 300_000,
signal,
maxRetries: 0,
},
);
} catch (error) {
if (
!(error instanceof RequestError) ||
error.raw.code !== 'resourceExhausted' ||
attempt + 1 === attempts
)
throw error;
await pause(retryAfter(error, fallbackMs), undefined, { signal });
}
}
throw new Error('attempt count was validated');
}
export function retryAfter(
error: RequestError,
fallbackMs: number,
): number {
const header = error.retryAfter?.trim();
if (header) {
const delay = /^\d+(?:\.\d+)?$/.test(header)
? Number(header) * 1000
: /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*,?\s/.test(header)
? Math.max(0, Date.parse(header) - Date.now())
: NaN;
if (Number.isFinite(delay) && delay >= 0) return delay;
}
return error.decoded.retryDelayMs ?? fallbackMs;
}
```
</details>
{{< /tab >}}
{{< /tabs >}}

View File

@@ -0,0 +1,142 @@
---
title: Errors and retries
description: Handle Docker Sandboxes API errors, recover from failed waits, and retry requests without duplicating work.
keywords: docker sandboxes API errors, REST errors, API retries, idempotency keys, rate limits
weight: 40
aliases:
- /ai/sandboxes/api/errors/
---
> [!NOTE]
> The Docker Sandboxes API and SDK are experimental. Features, interfaces,
> and behavior may change.
Before retrying a failed request, check whether the service already started
the work. For example, a create request can succeed even if your application
loses the response. Retrying without checking can create a second sandbox.
## Read an error response
API errors contain a `code`, a `message`, and optional typed `details`. Use the
code to decide how to respond. Several codes share an HTTP status, so the
status alone might not explain the failure.
| Code | What to do |
| --- | --- |
| `invalidArgument` | Correct the malformed request or unsupported value before retrying. |
| `unauthenticated` | Obtain a valid credential to replace the missing, invalid, or expired one. |
| `permissionDenied` | Check that your credentials have permission for the action. |
| `notFound` | Check the resource name and request URL. Cloud also returns this code for routes it doesn't serve. |
| `failedPrecondition` | Check the resource state, required features, and any `If-Match` header. |
| `resourceExhausted` | Check the error details for a quota, rate limit, or request-size limit. |
| `unimplemented` | Check whether the backend supports the requested feature. |
| `unavailable` | Retry after a delay, once you know the retry won't duplicate work. |
For more detail, inspect `google.rpc.ErrorInfo` in the error's `details`, if
present. Use its `reason` and `domain` fields together with the error code to
handle specific causes. Avoid matching the free-form message text, and
handle responses even when they include detail types you don't recognize.
## Recover from a failed wait
If the API returns HTTP 202, the work is still in progress. Keep reading the
resource until it reaches the state you need or fails. The resource's `failure`
field describes a failure that occurs after the initial request succeeds.
If a wait times out or is canceled, the action can still finish. Inspect the
resource before trying again or deleting it. SDK wait helpers report
`WaitError`, which includes the last resource the client received and any
failure details.
If the client never received a resource, retry the original request with its
idempotency key to recover the response. See
[Retry without duplicating work](#retry-without-duplicating-work).
## Retry without duplicating work
An idempotency key identifies one request, so the service can return its
original response if you send it again. For example, repeating a sandbox
creation request with the same key returns the first sandbox instead of
creating another one.
The SDK generates a key for each supported mutation unless you supply one.
For direct API calls, include an `Idempotency-Key` header in the original
request. Keep the key and the exact request, including any `If-Match` value,
for retries.
The service keeps accepted results for at least 24 hours. A replay returns
the original response, so read the resource afterward to check its latest
state.
Use the same key only when repeating the same request. Changing the request
under that key causes an error, and using a different key submits another
action. Send the header only for operations that support it:
| Operations | `Idempotency-Key` |
| --- | --- |
| Create a sandbox, image, process, port, snapshot, secret, or volume | Optional |
| Restore a snapshot | Optional |
| Start, stop, or delete a sandbox | Optional |
| Update a secret | Optional |
| Update a sandbox | Required |
Other operations don't accept an idempotency key.
Once you know a create request succeeded, poll the returned resource to wait
for completion. Retry only when you need to recover from a failure or a lost
response. For temporary failures, wait between retries and limit the number
of attempts.
Process creation also supports an idempotency key. Recover the creation
response with the same key before starting another process. Process input,
signals, and file writes aren't covered by that key. Check the outcome
before repeating those actions.
## Account for SDK retries
The SDK makes up to two additional attempts for eligible transient failures.
If your application manages its own retry loop, set `maxRetries: 0` for the
call to disable automatic retries. Combining both policies can produce more
attempts than you intended.
Automatic retries within a call reuse its idempotency key. When retrying from
your application, pass the original key in the call options as
`idempotencyKey`. Otherwise, a separate create call can generate a different
key and create another sandbox.
Limit both retry attempts and elapsed time, and honor server retry delays.
See [Request rate limits](limits.md#request-rate-limits) for how rate limits
differ from resource quotas.
## Handle concurrent changes
To avoid changing a resource that someone else has modified, send its `etag`
in the `If-Match` header. An etag identifies the version of the resource you
read. Operations such as sandbox updates and deletion require this header.
The SDK sends the etag stored in the resource object you're using.
For direct API requests, pass the etag exactly as returned, including its
quotes. A missing required header returns HTTP 428, and a stale etag returns
HTTP 412.
If the etag is stale, read the resource again and decide whether your change
is still appropriate. In the SDK, use the object returned by `refresh()` for
the next operation. The original object still has the old etag. Use a
different idempotency key for a request with an updated etag.
## Check command results
An API request can succeed even when the command it runs fails. Check the
command result's exit code and output separately from request and wait errors.
A nonzero exit code reports a command failure.
## Set a timeout for commands
To limit how long `processes.run()` waits for a command, pass `timeoutMs` in
the second argument. For example,
`sandbox.processes.run(input, { timeoutMs: 300_000 })` waits up to five minutes.
Timing out or canceling the call stops local waiting. It doesn't kill the
process in the sandbox.
Without `timeoutMs`, the overall run has no timeout. Individual requests to
create the process and read its output have a 30-second timeout.

View File

@@ -0,0 +1,125 @@
---
title: Run your first cloud sandbox
linkTitle: Get started
description: Use the TypeScript SDK to create a cloud sandbox, run a command, and delete the sandbox.
keywords: Docker Sandboxes API tutorial, cloud sandbox TypeScript, create sandbox, sandbox SDK quickstart
weight: 10
aliases:
- /ai/sandboxes/api/get-started/
---
> [!NOTE]
> The Docker Sandboxes API and SDK are experimental. Features, interfaces,
> and behavior may change.
Create a cloud sandbox, run a command inside it, and delete it using the Docker
Sandboxes TypeScript SDK. This tutorial uses the bundled `shell` kit to print
`Hello from Docker Sandboxes`. You don't need an AI agent or a model provider
API key to run it.
## Prerequisites
To follow this tutorial, you need:
- A Docker account with an active
[Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access)
- Node.js 20 or later and npm
## Create a project
Create a directory and initialize a Node.js project:
```console
$ mkdir sandboxes-api-tutorial
$ cd sandboxes-api-tutorial
$ npm init --yes
$ npm pkg set type=module
```
Install the SDK and TypeScript tooling:
```console
$ npm install @docker/sandboxes
$ npm install --save-dev tsx typescript @types/node
```
## Create and use a sandbox
A kit supplies the sandbox's image and configuration for an agent or tool.
The `shell` kit provides the environment for this example.
The SDK launches it with the default `small` compute size: two CPUs and
4 GiB of memory. Cloud compute is billed to your subscription. See
[Compute sizes and limits](limits.md) for other sizes.
Create a file named `index.ts` with the following code. The program prompts
you to sign in, creates a sandbox, runs a command, and deletes the sandbox.
```typescript
import { oauth, Sandboxes } from '@docker/sandboxes';
const client = new Sandboxes({
auth: oauth({
onVerification({ verificationUriComplete, verificationUri, userCode }) {
console.log(`Open ${verificationUriComplete ?? verificationUri}`);
console.log(`Verification code: ${userCode}`);
},
}),
});
try {
const sandbox = await client.kits.launchAndWait('shell');
console.log('Sandbox:', sandbox.name);
const result = await sandbox.processes.run(
{ args: ['echo', 'Hello from Docker Sandboxes'] },
{ timeoutMs: 300_000 },
);
console.log(result.stdout.trim());
const latest = await sandbox.refresh();
const deleting = await latest.delete({ force: true });
await deleting?.waitUntilDeleted();
console.log('Deleted', sandbox.name);
} finally {
await client.close();
}
```
`kits.launchAndWait` creates the sandbox and waits until it's running.
`processes.run` runs the command and collects its output.
The program then reads the sandbox's latest state, deletes it, and waits for
deletion to finish. The `force` option permits deletion while the sandbox is
running.
The SDK handles sign-in, access tokens, and the connection to the sandbox.
Closing the client releases its local resources.
## Run the program
Run the program with `tsx`:
```console
$ npx tsx index.ts
```
Open the printed verification URL and sign in with the Docker account that
has your cloud subscription. The program continues after sign-in. On success,
it prints `Hello from Docker Sandboxes`, then confirms sandbox deletion.
For CI jobs and other unattended applications, use
[PAT authentication](authentication.md#authenticate-automation-with-a-pat).
If the program stops after printing the sandbox name, retrieve the sandbox
with `client.get(name)` and delete it when you're finished. Closing the client
doesn't delete the sandbox.
## Next steps
- Explore the [SDK cookbook](cookbook/_index.md) for examples you can use in
your application.
- If you use a kit that clones a repository or installs tools,
[wait for kit setup](concepts.md#wait-for-kit-setup) before using its results.
- Read [API concepts](concepts.md) to learn about resource names, lifecycle
states, and supported Cloud options.
- Review [Errors and retries](errors.md) before adding recovery logic.

View File

@@ -0,0 +1,43 @@
---
title: Install the Docker Sandboxes SDK
linkTitle: Install the SDK
description: Install and use the Docker Sandboxes SDK in your JavaScript or TypeScript application.
keywords: docker sandboxes SDK, TypeScript SDK, JavaScript, npm, install sandbox SDK
weight: 5
aliases:
- /ai/sandboxes-api/sdks/
- /ai/sandboxes/api/sdks/
---
> [!NOTE]
> The Docker Sandboxes API and SDK are experimental. Features, interfaces,
> and behavior may change.
Use the Docker Sandboxes SDK to create and manage cloud sandboxes from
JavaScript or TypeScript. The SDK includes methods for running commands,
transferring files, and waiting for a sandbox to start or stop.
You don't need the Docker CLI to use the SDK.
## Install the SDK
With Node.js 20 or later, install the SDK in your project:
```console
$ npm install @docker/sandboxes
```
Import `Sandboxes` from `@docker/sandboxes`.
## Connect to Cloud Sandboxes
Before connecting, [activate a Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access)
for your Docker account.
Configure your client with browser sign-in for interactive use or a personal
access token for automation. The SDK supplies the service URL and manages
access tokens. See [Authentication and authorization](authentication.md) for
setup instructions.
Follow [Run your first cloud sandbox](get-started.md) for a complete TypeScript
example that creates a sandbox, runs a command, and deletes it.

View File

@@ -0,0 +1,88 @@
---
title: Compute sizes and limits
description: Choose a Cloud Sandboxes compute size and plan for account quotas and request rate limits.
keywords: cloud sandboxes, compute sizes, CPU, memory, quotas, rate limits
weight: 50
aliases:
- /ai/sandboxes/api/limits/
---
> [!NOTE]
> The Docker Sandboxes API and SDK are experimental. Features, interfaces,
> and behavior may change.
Choose a compute size for each sandbox and keep your application's resource
usage within your account's quotas. Request rate limits also constrain how
quickly your application can send API requests.
## Compute sizes
Cloud Sandboxes supports these CPU and memory pairs:
| SDK size name | CPUs | Memory | Memory in MiB |
| --- | ---: | ---: | ---: |
| `micro` | 1 | 2 GiB | 2048 |
| `small` | 2 | 4 GiB | 4096 |
| `medium` | 4 | 8 GiB | 8192 |
| `large` | 8 | 16 GiB | 16384 |
| `xl` | 16 | 32 GiB | 32768 |
The kit launch helpers default to `small` when you omit `resources`. To select
another size, pass its name:
```typescript
const sandbox = await client.kits.launchAndWait('shell', {
resources: 'medium',
});
```
For a registry image, specify a size in `client.create()`, such as
`resources: 'small'`. You can also pass an explicit CPU and memory pair:
`resources: { cpus: 2, memoryMib: 4096 }`.
Named sizes are an SDK convenience. In direct REST requests, supply both
`resources.cpus` and `resources.memoryMib`. CPU and memory aren't independent
settings: for example, 1 CPU with 1024 MiB is not a supported pair.
When creating from an existing image resource with `image`, omit resource
settings because the image supplies them.
Your account access and available capacity determine whether a request can
be accepted. See [Billing](/manuals/agentic-platform/signup.md#billing) for
pricing and usage information.
## Account quotas
The default quotas apply across an account:
| Resource | Default limit |
| --- | ---: |
| Concurrent sandboxes | 10 |
| Stored sandboxes | 50 |
| Volumes | 100 |
| Secrets | 100 |
| Images being prepared at the same time | 3 |
Your account can have different quotas. Confirm your account's limits with
Docker before planning a workload that depends on a particular allowance.
Stopping a sandbox releases its concurrency slot unless the sandbox is
configured as always-on. Restarting a stopped sandbox requires a concurrency
slot. A stopped sandbox still counts toward the stored sandbox quota.
Delete sandboxes you no longer need to reduce stored usage.
Handle quota errors even if you checked usage before creating a resource.
Other applications can consume the remaining allowance between requests.
Reduce concurrency or remove unused resources before retrying.
## Request rate limits
Rate limits control how quickly you can send requests and can vary by
operation. When a request reaches a rate limit, wait before retrying and
honor any delay specified by the server. Limit concurrent requests and set
bounds on retry attempts and elapsed time.
Check the error details to distinguish a rate limit from a resource quota.
Waiting can resolve a rate limit, but exceeding a quota requires reducing
resource usage. See [Errors and retries](errors.md) for how to retry without
duplicating work.

View File

@@ -0,0 +1,13 @@
---
title: Docker Sandboxes API reference
linkTitle: API reference
description: HTTP operations, request fields, and responses for the Docker Sandboxes API.
keywords: Docker Sandboxes API, REST API, OpenAPI, API reference
weight: 70
build:
render: never
list: local
params:
sidebar:
goto: /reference/api/sandboxes/latest/
---

View File

@@ -37,6 +37,9 @@ $ sbx run claude
See the [get started guide](get-started.md) for a first-session walkthrough, or
jump to the [usage guide](usage.md) for basic commands.
To create and manage cloud sandboxes from your application, see
[Sandboxes API and SDK](../sandboxes-api/_index.md).
## Learn more
- [Agents](agents/) — supported agents and per-agent configuration

View File

@@ -3,6 +3,7 @@ title: Reference documentation
linkTitle: Reference
layout: wide
description: Find reference documentation for the Docker platform’s various APIs, CLIs, and file formats
keywords: Docker reference, API, CLI, file formats
params:
icon: command-line
notoc: true
@@ -30,6 +31,10 @@ params:
icon: cpu-chip
link: /reference/cli/dockerd/
grid_apis:
- title: Sandboxes API (Experimental)
description: Create cloud sandboxes, run processes, and manage sandbox resources.
icon: code-bracket
link: /reference/api/sandboxes/latest/
- title: Engine API
description: The main API for Docker, provides programmatic access to a daemon.
icon: code-bracket

View File

@@ -15,6 +15,9 @@
{{- if eq $api.id "ai-governance" }}
{{ $aliases = slice "/reference/api/ai-governance/" "/ai/sandboxes/governance/api/" }}
{{ end -}}
{{- if eq $api.id "sandboxes" }}
{{ $aliases = slice "/reference/api/sandboxes/" }}
{{ end -}}
{{- $.AddPage (dict "linkTitle" "Latest" "path" $path "url" $api.url "aliases" $aliases "kind" "section" "title" (printf "%s API %v" $api.title $api.version) "description" (printf "%s HTTP API reference, version %v." $api.title $api.version) "layout" "api-docs" "weight" 1 "params" (dict "view" "overview" "apiID" $api.id "keywords" (slice "API" $api.product))) -}}
{{- range $op := $api.operations -}}
{{- $.AddPage (dict "path" (printf "%s/operations/%s" $path (crypto.SHA256 $op.id)) "url" $op.url "title" $op.summary "description" (printf "%s %s — %s API %v." $op.method $op.path $api.title $api.version) "layout" "api-docs" "params" (dict "view" "operation" "apiID" $api.id "operationID" $op.id "keywords" (slice $op.method $op.path $op.id))) -}}

View File

@@ -0,0 +1,7 @@
---
title: Docker Sandboxes API
description: HTTP API reference for Docker Cloud Sandboxes.
keywords: Docker Sandboxes, API reference, OpenAPI
build:
render: never
---

File diff suppressed because it is too large Load Diff

View File

@@ -86,3 +86,7 @@ screens.
Callbacks and webhook navigation are unsupported and fail validation. Request
examples are POSIX shell templates; they do not make service calls.
Examples are optional. Supplied examples must validate against their schemas.
When a request body has no example, the generated cURL command reads from
`request-body` and directs the reader to prepare that file from the schema.

View File

@@ -8,8 +8,8 @@ export default async function verify(page, base = "http://localhost:1314") {
await page.setViewportSize({ width: 1440, height: 1000 });
await page.goto(base + "/reference/api/");
assert(
(await page.locator(".api-card").count()) === 5,
"Five API catalog entries",
(await page.locator(".api-card").count()) === 6,
"Six API catalog entries",
);
assert(
(await page.locator("nav.navbar-font").count()) === 1,
@@ -88,6 +88,41 @@ export default async function verify(page, base = "http://localhost:1314") {
(await page.locator('[data-api-view="overview"]').count()) === 1,
"Governance alias reaches generated overview",
);
await page.goto(base + "/ai/sandboxes-api/");
assert(
(await page
.locator('nav.navbar-font a[href="/reference/api/sandboxes/latest/"]')
.count()) === 1,
"Sandboxes manual links to the API reference in the sidebar",
);
await page.goto(base + "/reference/api/sandboxes/");
await page.waitForURL("**/reference/api/sandboxes/latest/");
assert(
(await page.locator("article").textContent()).includes("experimental"),
"Sandboxes alias reaches the experimental API overview",
);
await page.goto(
base + "/reference/api/sandboxes/latest/operations/download/",
);
assert(
(await page.locator("article").textContent()).includes("itemEncoding"),
"File download renders stream encoding",
);
await page.goto(
base + "/reference/api/sandboxes/latest/operations/interact/",
);
assert(
(await page.locator("article").textContent()).includes(
"WebSocket session",
) && (await page.locator("[data-api-copy]").count()) === 0,
"Interactive processes show WebSocket requirements instead of a plain HTTP example",
);
assert(
await page.evaluate(
() => document.documentElement.scrollWidth <= innerWidth + 1,
),
"Sandboxes operation fits a narrow screen",
);
const context = await page
.context()
.browser()

View File

@@ -100,14 +100,15 @@ type Diagnostic struct {
Message string `json:"message"`
}
type Source struct {
ID string `json:"id"`
Product string `json:"product"`
Title string `json:"title"`
Source string `json:"source"`
Owner string `json:"owner"`
Manual string `json:"manual"`
Connection string `json:"connection"`
Guides []string `json:"guides"`
Experimental bool `json:"experimental,omitempty"`
ID string `json:"id"`
Product string `json:"product"`
Title string `json:"title"`
Source string `json:"source"`
Owner string `json:"owner"`
Manual string `json:"manual"`
Connection string `json:"connection"`
Guides []string `json:"guides"`
}
type Registry struct{ resources map[string]any }
@@ -539,12 +540,7 @@ func (d *Document) validate(metaDir string) {
d.issue("S8", str(param["pointer"])+"/description", "Parameter description requires editorial review")
}
}
for _, variant := range arr(op["variants"]) {
v := obj(variant)
if str(v["media"]) != "" && len(arr(v["examples"])) == 0 {
d.issue("S11", str(v["pointer"]), "Media variant needs a reviewed example or transfer fixture")
}
}
}
}
func (d *Document) schemaExamples(s any, p string) {
@@ -646,7 +642,7 @@ func (d *Document) variants(op Object, p string) []any {
for _, media := range keys(content) {
v := obj(content[media])
a := Object{"direction": direction, "status": status, "description": container["description"], "headers": container["headers"], "media": media, "pointer": ptr + "/content/" + esc(media), "examples": d.mediaExamples(v), "required": container["required"]}
for _, k := range []string{"schema", "itemSchema", "encoding"} {
for _, k := range []string{"schema", "itemSchema", "encoding", "itemEncoding", "prefixEncoding"} {
if s, ok := v[k]; ok {
a[k] = s
}

View File

@@ -47,6 +47,36 @@ func TestActualDialectAndReferences(t *testing.T) {
t.Fatal("duplicate overridden parameter")
}
}
func TestSequentialMediaEncodingPreserved(t *testing.T) {
d := fixture(t, "valid.yaml")
op := obj(obj(obj(d.Root["paths"])["/events"])["get"])
response := obj(obj(op["responses"])["200"])
encoding := Object{"contentType": "application/json", "headers": Object{
"Content-Disposition": Object{"schema": Object{"type": "string"}},
}}
response["content"] = Object{"multipart/mixed": Object{
"itemSchema": true, "example": jsonNumber("0"), "itemEncoding": encoding,
}}
d.validate("")
if len(d.Diagnostics) != 0 {
t.Fatalf("sequential media fixture must be valid: %+v", d.Diagnostics)
}
variants := d.variants(op, "/paths/~1events/get")
if len(variants) != 1 || !reflect.DeepEqual(obj(variants[0])["itemEncoding"], encoding) {
t.Fatalf("stream content types and part headers were lost: %#v", variants)
}
}
func TestWebSocketDoesNotProducePlainHTTPRequest(t *testing.T) {
op := Object{"method": "GET", "path": "/interact", "raw": Object{
"x-websocket": Object{"subprotocol": "sandboxes.v1"},
}}
command, notes := curlExample(Source{}, op)
if command != "" || len(notes) == 0 || !strings.Contains(notes[0], "WebSocket") {
t.Fatalf("WebSocket operation must describe its client requirements, not a plain HTTP request: %q %v", command, notes)
}
}
func jsonNumber(s string) any {
v, e := js.UnmarshalJSON(strings.NewReader(s))
if e != nil {
@@ -352,3 +382,48 @@ func TestExternalDynamicReference(t *testing.T) {
t.Fatalf("external dynamic reference rejected: %v", d.Diagnostics)
}
}
func TestExamplesAreOptionalButValidatedWhenPresent(t *testing.T) {
d := fixture(t, "valid.yaml")
var remove func(any)
remove = func(value any) {
switch v := value.(type) {
case map[string]any:
delete(v, "example")
delete(v, "examples")
for _, child := range v {
remove(child)
}
case []any:
for _, child := range v {
remove(child)
}
}
}
remove(d.Root)
d.validate("")
if len(d.Diagnostics) != 0 {
t.Fatalf("missing examples must be accepted: %+v", d.Diagnostics)
}
invalid := fixture(t, "invalid-example.yaml")
invalid.validate("")
for _, diagnostic := range invalid.Diagnostics {
if diagnostic.Rule == "example" {
return
}
}
t.Fatal("invalid supplied example must still fail")
}
func TestRequestWithoutExampleUsesBodyFile(t *testing.T) {
op := Object{"method": "POST", "path": "/jobs", "servers": []any{Object{"url": "https://api.test"}},
"variants": []any{Object{"direction": "Request", "media": "application/json", "required": true, "examples": []any{}}},
}
command, notes := curlExample(Source{}, op)
if !strings.Contains(command, "--data-binary @request-body") || strings.Contains(command, "--data-raw") {
t.Fatalf("request must use a body file, not an invented payload: %s", command)
}
if !strings.Contains(strings.Join(notes, " "), "Prepare request-body") {
t.Fatalf("missing body instructions: %v", notes)
}
}

View File

@@ -46,7 +46,7 @@ func (d *Document) model() Object {
op["references"] = refs(op["raw"], schemaURLs)
op["requestSchema"] = firstRequestSchema(op)
}
return Object{"id": d.Source.ID, "product": d.Source.Product, "title": d.Source.Title, "version": obj(d.Root["info"])["version"], "description": obj(d.Root["info"])["description"], "url": route(d.Source.ID), "manual": d.Source.Manual, "guides": d.Source.Guides, "connection": d.Source.Connection, "servers": d.Root["servers"], "securitySchemes": obj(d.Root["components"])["securitySchemes"], "tags": d.Root["tags"], "operations": ops, "schemas": schemas, "schemaURLs": schemaURLs, "digest": d.Digest, "owner": d.Source.Owner, "source": d.Source.Source, "sourceURL": "/" + strings.TrimPrefix(d.Source.Source, "content/"), "diagnostics": d.Diagnostics, "schemaCount": d.SchemaCount, "exampleCount": d.ExampleCount}
return Object{"experimental": d.Source.Experimental, "id": d.Source.ID, "product": d.Source.Product, "title": d.Source.Title, "version": obj(d.Root["info"])["version"], "description": obj(d.Root["info"])["description"], "url": route(d.Source.ID), "manual": d.Source.Manual, "guides": d.Source.Guides, "connection": d.Source.Connection, "servers": d.Root["servers"], "securitySchemes": obj(d.Root["components"])["securitySchemes"], "tags": d.Root["tags"], "operations": ops, "schemas": schemas, "schemaURLs": schemaURLs, "digest": d.Digest, "owner": d.Source.Owner, "source": d.Source.Source, "sourceURL": "/" + strings.TrimPrefix(d.Source.Source, "content/"), "diagnostics": d.Diagnostics, "schemaCount": d.SchemaCount, "exampleCount": d.ExampleCount}
}
func refs(v any, urls Object) []any {
found := map[string]bool{}
@@ -114,6 +114,9 @@ func responseMedia(op Object) string {
}
func curlExample(src Source, op Object) (string, []string) {
if len(obj(obj(op["raw"])["x-websocket"])) > 0 {
return "", []string{"Use a WebSocket client with the subprotocol, credential transport, and frame schemas documented for this operation."}
}
notes := []string{}
args := []string{"curl"}
if op["method"] == "HEAD" {

View File

@@ -39,6 +39,20 @@
"manual": "/manuals/ai/sandboxes/governance/",
"connection": "hosted",
"guides": []
},
{
"id": "sandboxes",
"product": "sandboxes",
"title": "Docker Sandboxes",
"source": "content/reference/api/sandboxes/api.yaml",
"owner": "docker/sbx-api",
"manual": "/manuals/ai/sandboxes-api/",
"connection": "hosted",
"experimental": true,
"guides": [
"/manuals/ai/sandboxes-api/authentication.md",
"/manuals/ai/sandboxes-api/get-started.md"
]
}
],
"legacyAPIs": [

47
hack/sandboxes/README.md Normal file
View File

@@ -0,0 +1,47 @@
# Sandboxes documentation import
The public cookbook export is published to the `generated` branch of
`docker/sbx-api` after its documentation CI passes. `source.json` pins both
the generated commit and the source commit recorded in that export's `SOURCE`
file. The guide list explicitly selects the recipes to publish.
`name-a-sandbox-and-find-it-again.md` is temporarily excluded pending a
working example. Restore its manifest entry and import it after verification.
Run the import from the repository root with Python 3 and `gh` authenticated for
`docker/sbx-api`:
```console
$ python3 hack/sync-sandboxes-docs.py
```
The import writes:
- Cookbook recipes to `content/manuals/ai/sandboxes-api/cookbook/`
- The public OpenAPI specification to
`content/reference/api/sandboxes/api.yaml`
Treat these files as generated. Make prose, example, and API corrections in
`docker/sbx-api`, then import the updated export. The cookbook's `_index.md`
is maintained in docker/docs, along with the overview, installation,
authentication, concepts, errors, and limits pages.
The import copies the selected recipes and OpenAPI specification byte for byte.
It excludes the upstream overview, installation page, and generated Markdown
API reference. Recipe installation links point to the canonical Docker Docs
installation page.
The specification is registered in `hack/api-docs/sources.json`. The shared API
renderer generates an overview and separate operation and schema pages under
`/reference/api/sandboxes/latest/`, in HTML and Markdown. Run
`./hack/api-docs/run.sh generate` before Hugo. Strict validation checks the
specification and its examples. Fix failures upstream and re-import the export.
To update, change both commit IDs in `source.json`, review additions or removals
in the upstream guide list, and run the import. Removed guides require explicit
deletion and a link check. Review the diff and build the site before committing.
The import does not select the latest release or push changes.
Release automation can update this manifest and run the same command in a pull
request once upstream publishes a release-to-export mapping. Keep the source
and generated commits paired rather than importing a moving branch.

View File

@@ -0,0 +1,43 @@
{
"repository": "docker/sbx-api",
"generatedCommit": "60d79c1d7744aa5ea5b3cfe871a2faaa5070e7ec",
"sourceCommit": "1a7f40266bc009e9602f3ca8a1cf809e1fc391f5",
"guides": [
"add-a-docker-credential-for-cloud-sandboxes.md",
"add-tools-with-kits.md",
"attach-persistent-storage.md",
"clone-a-cloud-sandbox.md",
"connect-to-cloud-with-a-bearer-token.md",
"control-what-a-sandbox-can-reach.md",
"copy-a-file-into-a-cloud-sandbox.md",
"create-your-first-sandbox.md",
"delete-a-cloud-sandbox.md",
"expose-a-port-from-a-cloud-sandbox.md",
"find-a-process-you-lost-track-of.md",
"get-a-stored-secret-into-a-sandbox.md",
"get-an-ssh-certificate.md",
"get-image-pull-urls.md",
"give-a-sandbox-an-mcp-gateway.md",
"handle-errors-and-degradation.md",
"keep-a-cloud-sandbox-running.md",
"let-a-stopped-sandbox-resume-on-demand.md",
"manage-cloud-secrets.md",
"manage-network-policies.md",
"page-through-and-filter-lists.md",
"read-files-out-of-a-sandbox.md",
"recover-when-the-endpoint-moves.md",
"register-and-manage-an-image.md",
"retry-without-creating-duplicates.md",
"run-a-complete-example.md",
"run-an-interactive-shell-in-a-cloud-sandbox.md",
"run-something-that-produces-real-output.md",
"run-work-across-many-sandboxes.md",
"run-your-first-command.md",
"run-your-own-container-image.md",
"send-values-the-api-accepts.md",
"snapshot-and-fork-a-sandbox.md",
"stop-and-restart-a-sandbox.md",
"what-your-workload-starts-with.md",
"work-within-the-limits.md"
]
}

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Import the pinned public cookbook export. See hack/sandboxes/README.md."""
import io
import json
from pathlib import Path
import subprocess
import tarfile
ROOT = Path(__file__).resolve().parent.parent
MANIFEST = ROOT / "hack/sandboxes/source.json"
GUIDES = ROOT / "content/manuals/ai/sandboxes-api/cookbook"
REFERENCE = ROOT / "content/reference/api/sandboxes"
def main():
source = json.loads(MANIFEST.read_text())
archive = subprocess.check_output([
"gh", "api",
f"repos/{source['repository']}/tarball/{source['generatedCommit']}",
])
with tarfile.open(fileobj=io.BytesIO(archive), mode="r:gz") as tar:
files = {}
for member in tar.getmembers():
if member.isfile():
path = member.name.split("/", 1)[1]
files[path] = tar.extractfile(member).read()
if files["SOURCE"].decode().strip() != source["sourceCommit"]:
raise SystemExit("The generated export does not match the recorded source commit")
# Prepare every output before changing the working tree. Fail if an upstream
# page disappears instead of silently dropping a published recipe.
outputs = {}
for name in source["guides"]:
if Path(name).name != name or not name.endswith(".md"):
raise SystemExit(f"Invalid guide filename: {name}")
outputs[GUIDES / name] = files[f"cookbook/outputs/guides/{name}"]
outputs[REFERENCE / "api.yaml"] = files["cookbook/outputs/api-reference/api.yaml"]
for destination, content in outputs.items():
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(content)
print(f"Imported {len(source['guides'])} recipes and the public OpenAPI specification")
if __name__ == "__main__":
main()

View File

@@ -1,11 +1,13 @@
{{ $data := index hugo.Data "api-reference" }}
{{ range $data.apis }}
{{ $manual := site.GetPage .manual }}
{{ if and $manual (eq $manual.RelPermalink $.RelPermalink) }}
<p
class="my-4 rounded border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900"
>
<a href="{{ .url }}">Explore the {{ .title }} API {{ .version }} →</a>
</p>
{{ if ne .Params.apiReferenceBanner false }}
{{ $data := index hugo.Data "api-reference" }}
{{ range $data.apis }}
{{ $manual := site.GetPage .manual }}
{{ if and $manual (eq $manual.RelPermalink $.RelPermalink) }}
<p
class="my-4 rounded border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900"
>
<a href="{{ .url }}">Explore the {{ .title }} API {{ .version }} →</a>
</p>
{{ end }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,19 @@
{{ $socket := .socket }}
{{ $api := .api }}
<section>
<h2>WebSocket session</h2>
<p>Subprotocol: <code>{{ $socket.subprotocol }}</code></p>
{{ range $key := slice "authentication" "browser" "reconnect" "terminalFrame" }}
{{ with index $socket $key }}<p>{{ . }}</p>{{ end }}
{{ end }}
{{ range $frame := slice (dict "key" "requestFrame" "title" "Client frames") (dict "key" "responseFrame" "title" "Server frames") (dict "key" "errorFrame" "title" "Error frames") }}
{{ with index $socket $frame.key }}
<h3>{{ $frame.title }}</h3>
{{ partial "api-reference/schema.html" (dict "schema" . "api" $api "depth" 0) }}
{{ end }}
{{ end }}
<details>
<summary>Handshake, resume fields, and close codes</summary>
<pre><code>{{ $socket | jsonify (dict "indent" " ") }}</code></pre>
</details>
</section>

View File

@@ -13,6 +13,9 @@
<hr />
</div>
{{ partial "api-reference/manual-link.html" . }}
{{ if and .CurrentSection.Params.recipeCatalog (not .IsSection) }}
<p><a href="{{ .CurrentSection.RelPermalink }}">Browse all recipes</a></p>
{{ end }}
{{ .Content }}
{{- if and (eq .Type "guides") (not .IsSection) (ne .CurrentSection .FirstSection) -}}
{{- with .PrevInSection -}}

View File

@@ -24,11 +24,14 @@
{{- $pages := slice }}
{{- range $sourcePages }}
{{- $isActive := or (eq page .) (page.IsDescendant .) }}
{{- if or (ne .Params.sitemap false) $isActive $revealHidden }}
{{- if and (or (not $section.Params.sidebar.activeChildrenOnly) $isActive) (or (ne .Params.sitemap false) $isActive $revealHidden) }}
{{- $pages = $pages | append . }}
{{- end }}
{{- end }}
{{- $ungrouped := where $pages "Params.sidebar.group" "==" nil }}
{{- if $section.Params.sidebar.activeChildrenOnly }}
{{- $ungrouped = $pages }}
{{- end }}
{{- range $ungrouped }}
{{- if and .IsSection (not .Params.apiID) }}
{{- template "renderList" (dict "section" . "revealHidden" $revealHidden) }}
@@ -36,7 +39,11 @@
{{- template "renderSingle" . }}
{{- end }}
{{- end }}
{{- range $section.Params.sidebar.groups }}
{{- $groups := $section.Params.sidebar.groups }}
{{- if $section.Params.sidebar.activeChildrenOnly }}
{{- $groups = slice }}
{{- end }}
{{- range $groups }}
<!-- Main titles -->
<div class="navbar-group">
<li class="navbar-group-font-title">
@@ -94,7 +101,7 @@
{{- $children := slice }}
{{- range $section.Pages }}
{{- $isActive := or (eq page .) (page.IsDescendant .) }}
{{- if or (ne .Params.sitemap false) $isActive $revealHidden }}
{{- if and (or (not $section.Params.sidebar.activeChildrenOnly) $isActive) (or (ne .Params.sitemap false) $isActive $revealHidden) }}
{{- $children = $children | append . }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,17 @@
{{ $recipes := where .Page.RegularPages "Params.sidebar.group" (.Get "group") }}
<ul
class="not-prose mb-8 grid list-none grid-cols-1 gap-x-8 p-0 md:grid-cols-2"
>
{{ range $recipes }}
<li class="border-t border-gray-200 py-4 dark:border-gray-700">
<a
class="font-semibold text-blue-600 hover:underline dark:text-blue-400"
href="{{ .RelPermalink }}"
>{{ .LinkTitle }}</a
>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
{{ .Description }}
</p>
</li>
{{ end }}
</ul>

View File

@@ -0,0 +1,3 @@
{{ range where .Page.RegularPages "Params.sidebar.group" (.Get "group") }}
- [{{ .LinkTitle }}]({{ .Permalink }}): {{ .Description }}
{{ end }}

View File

@@ -37,7 +37,7 @@
Hosted service
{{ end }}</span
>
<h2>{{ .title }}</h2>
<h2>{{ .title }}{{ if .experimental }}· Experimental{{ end }}</h2>
<p>API {{ .version }} · {{ len .operations }} operations</p>
<span>Explore reference →</span></a
>
@@ -51,7 +51,7 @@
Hosted service
{{ end }}</span
>
<h2>{{ .title }}</h2>
<h2>{{ .title }}{{ if .experimental }}· Experimental{{ end }}</h2>
<p>{{ .description }}</p>
<span>Explore reference →</span>
</a>
@@ -63,6 +63,15 @@
<a href="{{ $api.url }}">{{ $api.title }}</a> /
<span>API {{ $api.version }}</span>
</nav>
{{ if $api.experimental }}
<p class="api-label">Experimental</p>
{{ if eq .Params.view "overview" }}
<p>
This API is experimental. Features, interfaces, and behavior may
change.
</p>
{{ end }}
{{ end }}
<div class="api-tools">
<label
>API version
@@ -207,6 +216,11 @@
{{ end }}
</p>
{{ end }}
{{ range .servers }}
{{ range $name, $variable := .variables }}
<p><code>{{ $name }}</code>: {{ $variable.description }}</p>
{{ end }}
{{ end }}
{{ if not .security }}
<p>
No HTTP authentication requirement is declared for this
@@ -231,6 +245,9 @@
{{ end }}
</ul>
{{ end }}
{{ with index .raw "x-websocket" }}
{{ partial "api-reference/websocket.html" (dict "socket" . "api" $api) }}
{{ end }}
<h2>Parameters</h2>
{{ if not .parameters }}
<p>No parameters are declared.</p>
@@ -307,6 +324,16 @@
<h4>Stream item</h4>
{{ partial "api-reference/schema.html" (dict "schema" $v.itemSchema "api" $api "depth" 0) }}
{{ end }}
{{ range $key := slice "encoding" "itemEncoding" "prefixEncoding" }}
{{ with index $v $key }}
<details>
<summary>
{{ $key }}: content types and part headers
</summary>
<pre><code>{{ . | jsonify (dict "indent" " ") }}</code></pre>
</details>
{{ end }}
{{ end }}
{{ if $v.examples }}
<div data-api-examples>
{{ if gt (len $v.examples) 1 }}
@@ -341,16 +368,23 @@
</div>
<aside class="api-request">
<div class="api-request-heading">
<h2>Example request</h2>
<span>Shell</span>
<h2>
{{ if .curl }}
Example request
{{ else }}
WebSocket client
{{ end }}
</h2>
{{ if .curl }}<span>Shell</span>{{ end }}
</div>
<p>
Replace placeholders and supply the required credentials or
request body.
</p>
<pre><code data-api-copy-source>{{ .curl }}</code></pre>
<button type="button" data-api-copy>Copy request</button
>{{ range .curlNotes }}<p>{{ . }}</p>{{ end }}
{{ if .curl }}
<p>
Replace placeholders and supply the required credentials or
request body.
</p>
<pre><code data-api-copy-source>{{ .curl }}</code></pre>
<button type="button" data-api-copy>Copy request</button>
{{ end }}{{ range .curlNotes }}<p>{{ . }}</p>{{ end }}
</aside>
</div>
{{ end }}

View File

@@ -15,6 +15,11 @@ Choose a Docker HTTP API:
API version: {{ $api.version }}
{{ if $api.experimental }}
> This API is experimental. Features, interfaces,
> and behavior may change.
{{ end }}
{{ if eq .Params.view "overview" }}
## Overview
@@ -28,7 +33,9 @@ API version: {{ $api.version }}
{{ end }}{{ end }}
{{ range $api.servers }}
Server: `{{ .url }}`
{{ end }}
{{ range $name,$variable := .variables }}
`{{ $name }}`: {{ $variable.description }}
{{ end }}{{ end }}
{{ if eq $api.connection "unix" }}
```console
curl --unix-socket /var/run/docker.sock http://localhost/v{{ $api.version }}/version
@@ -68,12 +75,15 @@ Deprecated operation.
{{ end }}
{{ range .servers }}
Server: `{{ .url }}`
{{ end }}
{{ range $name,$variable := .variables }}
`{{ $name }}`: {{ $variable.description }}
{{ end }}{{ end }}
Effective security: alternatives are OR; schemes within an alternative are AND. An empty array declares no HTTP authentication requirement.
```json
{{ .security | jsonify (dict "indent" " ") }}
```
{{ if .curl }}
## Example request
Replace placeholders and provide the required credentials or request body.
@@ -81,9 +91,27 @@ Replace placeholders and provide the required credentials or request body.
```console
{{ .curl }}
```
{{ end }}
{{ range .curlNotes }}
{{ . }}
{{ end }}
{{ with index .raw "x-websocket" }}
## WebSocket session
Subprotocol: `{{ .subprotocol }}`
{{ .authentication }}
{{ .browser }}
{{ .reconnect }}
{{ .terminalFrame }}
```json
{{ . | jsonify (dict "indent" " ") }}
```
{{ end }}
## Parameters
{{ range .parameters }}
### {{ .name }}
@@ -125,6 +153,15 @@ Headers:
{{ . | jsonify (dict "indent" " ") }}
```
{{ end }}
{{ $variant := . }}
{{ range $key := slice "encoding" "itemEncoding" "prefixEncoding" }}
{{ with index $variant $key }}
{{ $key }}:
```json
{{ . | jsonify (dict "indent" " ") }}
```
{{ end }}{{ end }}
{{ range .examples }}
{{ .name }}:
```{{ .language }}