diff --git a/src/content/docs/sandbox/coding-agents/claude-code.mdx b/src/content/docs/sandbox/coding-agents/claude-code.mdx index b684ded3add..e11a3b99275 100644 --- a/src/content/docs/sandbox/coding-agents/claude-code.mdx +++ b/src/content/docs/sandbox/coding-agents/claude-code.mdx @@ -62,7 +62,7 @@ Run [Claude Code](https://code.claude.com/docs/en/overview) in a Linux sandbox t -3. In `src/outbound.ts`, make sure the `Outbound` entrypoint removes the `x-api-key` header before it adds the gateway token: +3. In `src/outbound.ts`, make sure that the `Outbound` entrypoint, which receives each HTTP and HTTPS request from the sandbox, removes the `x-api-key` header before it adds the gateway token: ```ts title="src/outbound.ts" const headers = new Headers(request.headers); @@ -72,7 +72,7 @@ Run [Claude Code](https://code.claude.com/docs/en/overview) in a Linux sandbox t Claude Code sends a placeholder API key. If the `x-api-key` header stays, AI Gateway forwards it to Anthropic, and the model request fails with `Invalid API key`. -4. In `src/sandbox.ts`, replace the `ResultEvent` constant with a schema for the final event from Claude Code: +4. In `src/sandbox.ts`, replace the event schema for the previous agent with this `ResultEvent` schema for the final event from Claude Code: ```ts title="src/sandbox.ts" const ResultEvent = z.object({ @@ -184,12 +184,14 @@ Run [Claude Code](https://code.claude.com/docs/en/overview) in a Linux sandbox t --data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit." ``` -3. Check the task until its state is `succeeded` or `failed`: +3. Check the task until its state is no longer `running`: ```sh curl "$WORKER_URL/sandboxes/claude-1/task" ``` + For what `succeeded`, `failed`, `lost`, and `none` mean, refer to [Read the outcome](/sandbox/get-started/build-a-coding-agent-runner/#read-the-outcome). + A `succeeded` task includes the final reply from Claude Code. 4. Read the changes: diff --git a/src/content/docs/sandbox/coding-agents/claude-managed-agents.mdx b/src/content/docs/sandbox/coding-agents/claude-managed-agents.mdx index a45a6755882..03e8f86696b 100644 --- a/src/content/docs/sandbox/coding-agents/claude-managed-agents.mdx +++ b/src/content/docs/sandbox/coding-agents/claude-managed-agents.mdx @@ -28,12 +28,12 @@ The template is alpha software. It is not yet stable and may contain bugs. ## How it works -When an agent session starts or ends, Anthropic sends a webhook to a Worker that the template deploys in your account. The Worker gives each session its own sandbox, applies an egress policy to its outbound traffic, saves the sandbox state while the session sleeps, and shuts the sandbox down when the session ends. +When an agent session starts or ends, Anthropic sends a webhook to a Worker that the template deploys in your account. The Worker gives each session its own sandbox, applies an egress policy (a set of rules for outbound requests) to its outbound traffic, saves the sandbox state while the session sleeps, and shuts the sandbox down when the session ends. Each agent runs in one of two sandbox types: - A Linux sandbox on [Containers](/containers/) gives the agent a shell and any process it needs to run. The template builds these sandboxes with [Sandbox SDK 0.x](/sandbox/sdk/). -- A [Dynamic Worker](/dynamic-workers/) starts in milliseconds and costs less than a container session, but runs code in the Workers runtime without a Linux shell or processes. +- A [Dynamic Worker](/dynamic-workers/) runs code in the Workers runtime without a Linux shell or processes. For more information about the trade-offs, refer to [Choose a sandbox environment](/sandbox/concepts/). @@ -42,11 +42,11 @@ For more information about the trade-offs, refer to [Choose a sandbox environmen The template also gives agents access to other Cloudflare products: - Agents reach private services over [Workers VPC](/workers-vpc/) and [Cloudflare Mesh](/mesh/) without exposing those services to the Internet. -- Outbound traffic passes through proxies that you configure. A proxy can add credentials to requests without the agent seeing them, restrict the domains an agent can reach, or run your own code on each request. +- Outbound traffic passes through proxies that you configure. A proxy is the handler that applies the egress policy to each request. It can add credentials to requests without the agent seeing them, restrict the domains an agent can reach, or run your own code on each request. - Each session can have its own email address for sending and receiving messages with [Email Service](/email-service/). - Agents can use headless browsers from [Browser Run](/browser-run/) to fetch pages, take screenshots, and control a browser over the Chrome DevTools Protocol. - Agents can generate images with [Workers AI](/workers-ai/). -- You add a tool by adding a function to one file. Tools run in the Worker with access to all of its bindings. +- You add a tool by adding a function to one file. Tools run in the Worker with access to all of its [bindings](/workers/runtime-apis/bindings/), the APIs that connect the Worker to other Cloudflare resources. - A dashboard lists agents and sessions, shows logs, and opens a shell in a running Linux sandbox. ## When to use it @@ -63,10 +63,20 @@ Use a self-managed Cloudflare environment when you need: Follow the [onboarding guide](https://github.com/cloudflare/claude-managed-agents#onboarding-guide) in the repository. It covers the Anthropic environment and webhook, secrets, D1 migrations, R2 credentials for snapshots, and dashboard security. Deploy with the **Deploy to Cloudflare** button, which builds the template in Workers Builds, or run `npm run deploy` from your terminal. +To check the deployment, open the URL of the Worker, create an agent, and start a session. The dashboard shows the logs of the session. + :::note You need a Workers Paid plan or an Enterprise account. The template runs Linux sandboxes on [Containers](/containers/), and runs Dynamic Workers and egress proxies through Worker Loader bindings. ::: +## Security + +Each session runs in its own sandbox. A Linux sandbox receives the Anthropic environment key as the `ANTHROPIC_ENVIRONMENT_KEY` environment variable, so code that runs in the session can read it. The Worker keeps `ANTHROPIC_API_KEY` and does not pass it to the sandbox. Code in a Dynamic Worker sandbox receives neither key. + +The template serves the dashboard at `/`, its API at `/api/*`, and terminal connections at `/ws/terminal` without authentication. Anyone who reaches these routes can start sessions, change secrets and egress policies, and open a shell in a running Linux sandbox. Protect them with [Cloudflare Access](/cloudflare-one/access-controls/applications/http-apps/self-hosted-public-app/) before you expose the deployment. Anthropic calls `/webhooks` without an Access login, so use a [path-specific policy](/cloudflare-one/access-controls/policies/app-paths/) that leaves `/webhooks` reachable. The Worker checks the signature of each webhook with `WEBHOOK_SECRET`. + +For setup steps, refer to [Securing access](https://github.com/cloudflare/claude-managed-agents/blob/main/docs/securing-access.md) in the template. For more information about what a sandbox exposes to the code inside it, refer to [Sandbox security](/sandbox/concepts/security/). + ## Template documentation The repository documents each capability: diff --git a/src/content/docs/sandbox/coding-agents/codex.mdx b/src/content/docs/sandbox/coding-agents/codex.mdx index 5da4dee2cf0..7cd7b596b93 100644 --- a/src/content/docs/sandbox/coding-agents/codex.mdx +++ b/src/content/docs/sandbox/coding-agents/codex.mdx @@ -128,9 +128,9 @@ Run the [Codex CLI](https://developers.openai.com/codex/cli) in a Linux sandbox `--dangerously-bypass-approvals-and-sandbox` turns off the approval prompts and the built-in command sandbox of Codex. The container limits what commands from Codex can reach instead. - A custom provider sends no API key, and the `Outbound` entrypoint adds the gateway token. + A custom provider sends no API key, and the `Outbound` entrypoint, which receives each HTTP and HTTPS request from the sandbox, adds the gateway token. - Codex reads the certificate for intercepted HTTPS from `SSL_CERT_FILE`, which `trustEnv` in the runner sets. + Codex reads the certificate for intercepted HTTPS from `SSL_CERT_FILE`. The runner sets it in `trustEnv`, the environment variables that tell tools in the container which certificate to trust. For more information, refer to [Start the sandbox](/sandbox/get-started/build-a-coding-agent-runner/#start-the-sandbox). 5. Replace the `outcome()` method: @@ -201,12 +201,14 @@ Run the [Codex CLI](https://developers.openai.com/codex/cli) in a Linux sandbox --data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit." ``` -3. Check the task until its state is `succeeded` or `failed`: +3. Check the task until its state is no longer `running`: ```sh curl "$WORKER_URL/sandboxes/codex-1/task" ``` + For what `succeeded`, `failed`, `lost`, and `none` mean, refer to [Read the outcome](/sandbox/get-started/build-a-coding-agent-runner/#read-the-outcome). + A `succeeded` task includes the final message that Codex wrote. 4. Read the changes: diff --git a/src/content/docs/sandbox/coding-agents/cursor.mdx b/src/content/docs/sandbox/coding-agents/cursor.mdx index 0ffe6ca9f97..265d85e9c2c 100644 --- a/src/content/docs/sandbox/coding-agents/cursor.mdx +++ b/src/content/docs/sandbox/coding-agents/cursor.mdx @@ -13,10 +13,10 @@ products: reviewed: 2026-09-24 --- -import { Steps } from "~/components"; +import { Steps, WranglerConfig } from "~/components"; import { VendorAgentConnections } from "~/components/react/sandbox/VendorAgentConnections"; -Run Cursor Cloud Agents on Cloudflare through [self-hosted machines](https://cursor.com/docs/cloud-agent/self-hosted). Each Cursor session assigned to the deployment runs in its own Linux sandbox: a [Container](/containers/) that one Durable Object starts for that session. +Run Cursor Cloud Agents on Cloudflare through [self-hosted machines](https://cursor.com/docs/cloud-agent/self-hosted). Each Cursor session assigned to the deployment runs in its own Linux sandbox: a [Container](/containers/) that one [Durable Object](/durable-objects/) starts for that session. Cursor hosts the agent loop, inference, and planning. Cloudflare runs commands, file edits, repository operations, and other tools inside infrastructure that you control. @@ -44,7 +44,7 @@ For more information, refer to [Cursor team pools](https://cursor.com/docs/cloud ## Deploy the template -The template deploys a Worker, a Durable Object namespace, a container application, an R2 bucket binding, and a cron trigger. +The template deploys a Worker, a Durable Object that coordinates each session, a container, an R2 bucket binding that connects the Worker to its snapshot bucket, and a [cron trigger](/workers/configuration/cron-triggers/) that runs the Worker on a schedule. It uses the [`Container` class](/containers/api/container-class/) from `@cloudflare/containers` and the `default` [scheduling policy](/containers/configuration/scheduling-policy/#use-the-default-scheduling-policy), so `max_instances` and image [rollouts](/containers/configuration/rollouts/) apply to all of its containers. 1. Clone the template and install its dependencies: @@ -88,6 +88,8 @@ The template deploys a Worker, a Durable Object namespace, a container applicati 6. In `wrangler.jsonc`, set `vars.CURSOR_POOL` to the Cursor team pool name: + + ```jsonc { "vars": { @@ -96,6 +98,8 @@ The template deploys a Worker, a Durable Object namespace, a container applicati } ``` + + 7. Set `containers[].max_instances` to the maximum number of concurrent sessions that the deployment must support. 8. Deploy the Worker and container: @@ -158,6 +162,8 @@ Repository snapshots are an optional cache for repository-bound agents. A snapsh 2. In `wrangler.jsonc`, set `vars.WORKER_PUBLIC_URL` to the deployed Worker URL: + + ```jsonc { "vars": { @@ -167,6 +173,8 @@ Repository snapshots are an optional cache for repository-bound agents. A snapsh } ``` + + 3. Deploy the updated configuration: ```sh @@ -190,7 +198,7 @@ A cron trigger runs the Worker every five minutes. Each run lists the sessions t The Worker claims each session with a unique worker ID. Cursor then assigns the session only to that Worker. A Durable Object for the session starts one container with the session environment and repository information. -The container stops after five minutes without work, or after `WORKER_IDLE_RELEASE_TIMEOUT_SECONDS` if you set that variable. The Durable Object also stops the container after eight hours, or after `MAX_RUN_LIFETIME_SECONDS` if you set that variable. +`WORKER_IDLE_RELEASE_TIMEOUT_SECONDS` sets how long the container waits without work before it stops. `MAX_RUN_LIFETIME_SECONDS` sets the longest time that the Durable Object keeps the container running. For the default values, refer to [`src/config.ts`](https://github.com/anysphere/cloudflare-workers/blob/main/src/config.ts) in the template. The container connects out to Cursor, so it needs no inbound port or public IP address. The Worker exposes only its health route and the optional snapshot routes. diff --git a/src/content/docs/sandbox/coding-agents/devin.mdx b/src/content/docs/sandbox/coding-agents/devin.mdx index 232c997ab30..5f33962f26e 100644 --- a/src/content/docs/sandbox/coding-agents/devin.mdx +++ b/src/content/docs/sandbox/coding-agents/devin.mdx @@ -15,7 +15,7 @@ reviewed: 2026-09-24 import { PackageManagers, Steps } from "~/components"; import { VendorAgentConnections } from "~/components/react/sandbox/VendorAgentConnections"; -[Devin](https://docs.devin.ai/cloud/outposts/overview) runs its agent loop in its own service. Each session works on a machine where Devin runs commands, edits files, and opens a browser. A Devin Outpost moves that machine to infrastructure that you choose. The Devin Outpost template runs each session in its own Linux sandbox: a [Container](/containers/) that one Durable Object starts for that session. +[Devin](https://docs.devin.ai/cloud/outposts/overview) runs its agent loop in its own service. Each session works on a machine where Devin runs commands, edits files, and opens a browser. A Devin Outpost moves that machine to infrastructure that you choose. The Devin Outpost template runs each session in its own Linux sandbox: a [Container](/containers/) that one [Durable Object](/durable-objects/) starts and stops for that session. ## Prerequisites @@ -43,7 +43,7 @@ For more information about outposts, refer to the [Devin Outposts overview](http ## Deploy with one click -The **Deploy to Cloudflare** button creates the Worker, the cron trigger, the Durable Object namespace, and the container application. +The **Deploy to Cloudflare** button deploys the Worker with its Durable Object, its container, and a [cron trigger](/workers/configuration/cron-triggers/) that runs the Worker on a schedule. [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/sandbox-sdk/tree/main/templates/devin) @@ -116,10 +116,12 @@ Deploy manually when you need to add dependencies, tools, or environment variabl ## Run a Devin session -In Devin, create a session and select your outpost from the **Virtual environment** menu. The Worker checks for new sessions every 10 seconds by default, so the sandbox for the session starts shortly after you create it. +In Devin, create a session and select your outpost from the **Virtual environment** menu. The Worker checks Devin for new sessions on a short interval, so the sandbox for the session starts shortly after you create it. ## How the template runs sessions +The template saves each session in a snapshot: a saved copy of the disk of a sandbox, so a later sandbox can start with the same files. + A cron trigger runs the Worker once a minute. During each run, the Worker asks Devin for the sessions of your outpost every 10 seconds, or every `DEVIN_RECONCILE_INTERVAL_MS` milliseconds if you set that variable. The Worker sends the status of each session to the Durable Object for that session: | Devin status | What the template does | @@ -136,12 +138,12 @@ Your Worker connects to Devin on a cron trigger and lists sessions. It sends eac -When Devin suspends a session, the Devin CLI exits. The sandbox keeps running until the Durable Object saves a snapshot of its disk. When the session continues, the sandbox starts from that snapshot and the Devin CLI claims the session again. The snapshot keeps the files and installed packages of the session. It does not keep running processes. If the Devin CLI exits while the session is still running, the template saves a snapshot and starts a new sandbox from it. +When Devin suspends a session, the Devin CLI exits. The template saves a snapshot only after the Devin CLI exits, and the sandbox keeps running until the template saves it. If a sandbox stops before then, the session continues from its previous snapshot, or from the image if it has none. When the session continues, the sandbox starts from the snapshot and the Devin CLI claims the session again. The snapshot keeps the files and installed packages of the session. It does not keep running processes. If the Devin CLI exits while the session is still running, the template saves a snapshot and starts a new sandbox from it. For more information, refer to [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/). :::note -The template saves a snapshot only after the Devin CLI exits. If a sandbox stops before then, the session continues from its previous snapshot, or from the image if it has none. The Worker API cannot delete snapshots, so the snapshot of a terminated session stays in your account. +Terminating a session removes the snapshot ID that the template stores. The snapshot stays in your account, because the Worker API cannot delete snapshots. For how long a snapshot lasts, refer to [Understand retention](/containers/guides/snapshots/#understand-retention). ::: ## Security diff --git a/src/content/docs/sandbox/coding-agents/index.mdx b/src/content/docs/sandbox/coding-agents/index.mdx index 61ece47ccad..c4ef84369a7 100644 --- a/src/content/docs/sandbox/coding-agents/index.mdx +++ b/src/content/docs/sandbox/coding-agents/index.mdx @@ -20,9 +20,9 @@ Coding agents such as Claude Code, Codex, and OpenCode can run a task on their o ## The agent runs in your sandbox -Your Worker starts the sandbox, holds the credentials, and decides which hostnames the sandbox can reach. The agent calls its model over HTTP, for example through [AI Gateway](/ai-gateway/). Your Worker adds the API token to each request, so the sandbox never receives it. +Your [Worker](/workers/), the application that receives your requests, starts the sandbox, holds the credentials, and decides which hostnames the sandbox can reach. The agent calls its model over HTTP, for example through [AI Gateway](/ai-gateway/). Your Worker adds the API token to each request, so the sandbox never receives it. -Each guide in this section changes only the agent-specific parts of one runner. Build the runner first: +The guides for command-line agents change only the agent-specific parts of one runner. Build the runner first: .workers.dev/webhook`. OpenAI must be able to reach this URL. @@ -189,7 +185,7 @@ To deploy from your terminal instead of with the button in step 2: npx wrangler login ``` -2. Generate a shared secret for the container cleanup endpoint, and save it: +2. Generate a shared secret for the cleanup endpoint, which stops the container of a session, and save it: ```bash openssl rand -hex 32 @@ -311,7 +307,7 @@ Container snapshots are in public beta. Features and behavior may change. When a session goes idle, the Worker snapshots the container filesystem before it stops the container. The next environment connection starts from that snapshot, so the files in `/workspace` come back. Running processes and memory do not. If the snapshot fails, the Worker leaves the container running and schedules another lifecycle check. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#snapshots-carry-files-to-the-next-instance). -A session uses its snapshot only while the session lasts. The Worker deletes the snapshot record of a session when the session fails, when a later lookup finds that the session no longer exists, or when you call the cleanup endpoint. The next session then starts from the image. The Worker does not delete the snapshot itself. +A session uses its snapshot only while the session lasts. After the Worker deletes the snapshot record of a session, as described in [Clean up](/sandbox/coding-agents/openai-agents-api/#clean-up), the next session starts from the image. The Worker does not delete the snapshot itself. With `EXECUTOR_SNAPSHOTS_ENABLED` set to `false`, each new executor starts with an empty `/workspace`. To keep files after a session ends, store them somewhere else, such as an R2 bucket. For more information, refer to [Mount an R2 bucket](/sandbox/files/mount-an-r2-bucket/). diff --git a/src/content/docs/sandbox/coding-agents/opencode.mdx b/src/content/docs/sandbox/coding-agents/opencode.mdx index 775012939b4..b3fc2394484 100644 --- a/src/content/docs/sandbox/coding-agents/opencode.mdx +++ b/src/content/docs/sandbox/coding-agents/opencode.mdx @@ -122,7 +122,7 @@ Run [OpenCode](https://opencode.ai) in a Linux sandbox to edit a GitHub reposito `--auto` approves every tool call that the OpenCode configuration does not deny. Without it, OpenCode rejects permission requests in a run without a terminal. The container limits what commands from OpenCode can reach. - The built-in `cloudflare-ai-gateway` provider in OpenCode reads the account and gateway IDs from the environment. For Anthropic models, it sends requests to the universal endpoint of the gateway: the gateway path with no suffix. The `Outbound` entrypoint in the runner allows that path and the paths under it. `CLOUDFLARE_API_TOKEN` is a placeholder, because OpenCode does not start without a token. The `Outbound` entrypoint replaces the token header with the gateway token. + The built-in `cloudflare-ai-gateway` provider in OpenCode reads the account and gateway IDs from the environment. For Anthropic models, it sends requests to `https://gateway.ai.cloudflare.com/v1//`, with no provider suffix. The `Outbound` entrypoint in the runner, which receives each HTTP and HTTPS request from the sandbox, allows that URL and the URLs under it. `CLOUDFLARE_API_TOKEN` is a placeholder, because OpenCode does not start without a token. The `Outbound` entrypoint replaces the token header with the gateway token. The `OPENCODE_DISABLE_*` variables make OpenCode use the model list built into the binary and turn off its update check, default plugins, language server downloads, and sharing. OpenCode still tries to install `@opencode-ai/plugin` from npm in the background. That request gets a `403` response from the `Outbound` entrypoint, and OpenCode continues. @@ -196,12 +196,14 @@ Run [OpenCode](https://opencode.ai) in a Linux sandbox to edit a GitHub reposito --data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit." ``` -3. Check the task until its state is `succeeded` or `failed`: +3. Check the task until its state is no longer `running`: ```sh curl "$WORKER_URL/sandboxes/opencode-1/task" ``` + For what `succeeded`, `failed`, `lost`, and `none` mean, refer to [Read the outcome](/sandbox/get-started/build-a-coding-agent-runner/#read-the-outcome). + A `succeeded` task includes the last text reply from OpenCode. 4. Read the changes: diff --git a/src/content/docs/sandbox/coding-agents/pi.mdx b/src/content/docs/sandbox/coding-agents/pi.mdx index e734bb6757d..064775f9a9f 100644 --- a/src/content/docs/sandbox/coding-agents/pi.mdx +++ b/src/content/docs/sandbox/coding-agents/pi.mdx @@ -129,7 +129,7 @@ Run the [Pi](https://github.com/earendil-works/pi) coding agent in a Linux sandb Pi runs its tools without approval prompts. The container limits what commands from Pi can reach. - Pi does not start without an API key, so `CLOUDFLARE_API_KEY` is a placeholder. Pi sends the key in the `cf-aig-authorization` header, and the `Outbound` entrypoint replaces it with the gateway token. `PI_OFFLINE`, `PI_SKIP_VERSION_CHECK`, and `PI_TELEMETRY` turn off the model catalog refresh, version check, and telemetry in Pi. + Pi does not start without an API key, so `CLOUDFLARE_API_KEY` is a placeholder. Pi sends the key in the `cf-aig-authorization` header, and the `Outbound` entrypoint, which receives each HTTP and HTTPS request from the sandbox, replaces it with the gateway token. `PI_OFFLINE`, `PI_SKIP_VERSION_CHECK`, and `PI_TELEMETRY` turn off the model catalog refresh, version check, and telemetry in Pi. Pi still loads `AGENTS.md` and `CLAUDE.md` files from the repository. Without the `--approve` flag, it skips the protected project resources in the `.pi` directory of the repository. Leave `--approve` off for repositories you do not trust. @@ -213,12 +213,14 @@ Run the [Pi](https://github.com/earendil-works/pi) coding agent in a Linux sandb --data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit." ``` -3. Check the task until its state is `succeeded` or `failed`: +3. Check the task until its state is no longer `running`: ```sh curl "$WORKER_URL/sandboxes/pi-1/task" ``` + For what `succeeded`, `failed`, `lost`, and `none` mean, refer to [Read the outcome](/sandbox/get-started/build-a-coding-agent-runner/#read-the-outcome). + A `succeeded` task includes the text of the last reply from Pi. 4. Read the changes: diff --git a/src/content/docs/sandbox/commands/follow-a-background-process.mdx b/src/content/docs/sandbox/commands/follow-a-background-process.mdx index 70ed347269e..539104e0f62 100644 --- a/src/content/docs/sandbox/commands/follow-a-background-process.mdx +++ b/src/content/docs/sandbox/commands/follow-a-background-process.mdx @@ -253,7 +253,7 @@ A development server prints a line, such as `ready`, when it accepts requests. T { "state": "matched", "line": "ready" } ``` - The pattern is an extended regular expression. The state is `exited` when the process ends without printing a matching line, `lost` when it ended without recording an exit code, and `timed-out` when the time limit passes first. The method returns `undefined` when no process uses the ID. A process that was still running when an earlier instance stopped is `lost`, even when its log matches, because `current()` checks the boot ID. + The pattern is an extended regular expression. The state is `exited` when the process ends without printing a matching line, `lost` when it ended without recording an exit code, and `timed-out` when the time limit passes first. The method returns `undefined` when no process uses the ID. A process that was still running when an earlier container stopped is `lost`, even when its log matches, because `current()` checks the boot ID. diff --git a/src/content/docs/sandbox/commands/index.mdx b/src/content/docs/sandbox/commands/index.mdx index 59e230c47a5..c6ae1351895 100644 --- a/src/content/docs/sandbox/commands/index.mdx +++ b/src/content/docs/sandbox/commands/index.mdx @@ -13,7 +13,7 @@ products: - durable-objects --- -Each of the following guides runs a command in a sandbox with `exec()`. They differ in how the output reaches the caller and in what ends the command: +In each of the following guides, your Durable Object calls [`exec()`](/containers/api/durable-object-container/#exec) to start a process in its running container. The guides differ in how the output reaches the caller and in what ends the command: | Guide | How the output reaches the caller | The command runs until | | ------------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------- | diff --git a/src/content/docs/sandbox/commands/open-a-terminal-in-the-browser.mdx b/src/content/docs/sandbox/commands/open-a-terminal-in-the-browser.mdx index 3aceecde1d4..c7350c6e57a 100644 --- a/src/content/docs/sandbox/commands/open-a-terminal-in-the-browser.mdx +++ b/src/content/docs/sandbox/commands/open-a-terminal-in-the-browser.mdx @@ -93,7 +93,7 @@ Expose a live shell in a Linux sandbox on a web page. The page runs [xterm.js](h -3. Add an inactivity timeout to your Durable Object. The constructor sets the timeout again when a restarted Durable Object finds the container running: +3. Add an inactivity timeout to your Durable Object. When the Durable Object [leaves memory and starts again](/sandbox/concepts/lifetime/#the-timeout-does-not-survive-a-restart), the constructor sets the timeout again if the container is still running: ```ts title="src/index.ts" const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; @@ -217,9 +217,9 @@ Expose a live shell in a Linux sandbox on a web page. The page runs [xterm.js](h } ``` - Each WebSocket gets its own tmux client. `tmux new-session -A` attaches the client to the named session, or creates the session if it does not exist. The handler writes keystrokes to the standard input of the client, applies resize messages, and forwards the client output unchanged. + Each WebSocket gets its own tmux client. The `pty` option runs the client attached to a pseudo-terminal, a terminal device that the client reads keystrokes from and draws its screen on. `tmux new-session -A` attaches the client to the named session, or creates the session if it does not exist. The handler writes keystrokes to the standard input of the client, applies resize messages, and forwards the client output unchanged. - The handler accepts the WebSocket with `accept()` instead of the [Hibernation WebSocket API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api). Hibernation would discard the process handle of the tmux client. + The handler accepts the WebSocket with `accept()`, which keeps the Durable Object in memory with the process handle of the tmux client. The [Hibernation WebSocket API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api) lets the Durable Object leave memory while the WebSocket stays open, which would discard that handle. The shell runs as `root`. The [`user` option of `exec()`](/containers/api/durable-object-container/#exec), such as `user: "1000:1000"`, changes the Linux user of the shell. It does not limit what the shell can read or change. For more information, refer to [Sandbox security](/sandbox/concepts/security/#everything-in-one-sandbox-is-shared). diff --git a/src/content/docs/sandbox/commands/run-a-server-in-the-background.mdx b/src/content/docs/sandbox/commands/run-a-server-in-the-background.mdx index 35886aba6b8..69900a10004 100644 --- a/src/content/docs/sandbox/commands/run-a-server-in-the-background.mdx +++ b/src/content/docs/sandbox/commands/run-a-server-in-the-background.mdx @@ -16,7 +16,7 @@ import { PackageManagers, Steps, TypeScriptExample } from "~/components"; Start a server that runs until the container stops, such as a development server, a tunnel, or a code kernel. Unlike a job, nothing waits for the server to exit. Your Durable Object starts it on first use, waits until it answers, and starts it again in each new container. -For a process that runs until it finishes, such as a build or an agent task, refer to [Run background processes](/sandbox/commands/run-background-processes/). If the sandbox runs one server and keeps no files that you need, you can make the server the main process instead, as in [Preview a web application](/sandbox/previews/). When the main process exits, the instance stops, and its files end with it. +For a process that runs until it finishes, such as a build or an agent task, refer to [Run background processes](/sandbox/commands/run-background-processes/). If the sandbox runs one server and keeps no files that you need, you can make the server the main process instead, as in [Preview a web application](/sandbox/previews/). The main process is the process that the container starts with, and the container stops when it exits. For more information, refer to [What stops an instance](/sandbox/concepts/lifetime/#what-stops-an-instance). ## Prerequisites @@ -136,17 +136,17 @@ For a process that runs until it finishes, such as a build or an agent task, ref - `SERVE` starts nothing when the server in the directory already runs, and a new container, which has no server, gets one. This also covers a Durable Object that restarts while its container keeps running. + `SERVE` starts nothing when the server in the directory already runs, and a new container, which has no server, gets one. This also covers a Durable Object that leaves memory and starts again while its container keeps running. - The process ID file also records the boot ID, which changes in every instance. A [snapshot](/sandbox/files/save-and-restore-a-workspace/) restores the file, and a new instance reuses the same process IDs, so a restored process ID can belong to an unrelated process. `current()` ignores a process ID from another instance. + The process ID file also records the boot ID, which changes each time a container starts. `current()` ignores a process ID from an earlier container, because a [snapshot](/sandbox/files/save-and-restore-a-workspace/) can restore the file, and the restored process ID can belong to an unrelated process. - `setsid` starts the server in a new process group, so `STOP` also stops the processes that the server starts. The shell that runs `SERVE` stays in the container until the server exits. Do not start the server with `&` instead: a shell starts background jobs with `SIGINT` ignored. + `setsid` starts the server in a new process group, a set of processes that one `kill` can signal together, so `STOP` also stops the processes that the server starts, unless a process moves to its own group. The shell that runs `SERVE` stays in the container until the server exits. Do not start the server with `&` instead: a shell starts background jobs with `SIGINT` ignored. `STOP` removes the process ID once the server exits, so a server started in the same directory right after it never reads as exited. A server that ignores `SIGTERM` receives `SIGKILL` after 10 seconds. `waitForServer()` treats any HTTP response as ready. If the server exits first, it throws with the end of the log. The log grows until the server stops, so keep the output of a busy server small, or send it to a [mounted bucket](/sandbox/files/mount-an-r2-bucket/). -2. Add a constructor to your Durable Object. It sets the inactivity timeout again when a restarted Durable Object finds the container running: +2. Add a constructor to your Durable Object. When the Durable Object [leaves memory and starts again](/sandbox/concepts/lifetime/#the-timeout-does-not-survive-a-restart), the constructor sets the inactivity timeout again if the container is still running: ```ts title="src/index.ts" export class MyContainer extends DurableObject { @@ -218,7 +218,7 @@ For a process that runs until it finishes, such as a build or an agent task, ref Requests that arrive together share one start. `startServer()` returns before the server records its process ID, so a second `SERVE` in that window would start a second server. The second server fails on the port, overwrites the process ID file, and leaves `STOP` unable to stop the first one. After the server answers, each request runs `SERVE` again, which starts nothing while the server runs and restarts it if it exited. - A running server does not keep the container running. Requests that reach the server through your Durable Object keep it running, as other requests do. When the inactivity timeout ends, the container stops, and the server stops with it. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#activity-keeps-an-instance-running). + A running server does not keep the container running. In this example, requests to the server keep the container running, and the inactivity timeout decides when it stops after the last request. The server stops with the container. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#activity-keeps-an-instance-running). 3. Add routes to your Worker that call these methods: diff --git a/src/content/docs/sandbox/commands/run-background-processes.mdx b/src/content/docs/sandbox/commands/run-background-processes.mdx index 55d0d479e48..cdf725a3b01 100644 --- a/src/content/docs/sandbox/commands/run-background-processes.mdx +++ b/src/content/docs/sandbox/commands/run-background-processes.mdx @@ -77,11 +77,11 @@ For a process that runs until the container stops, such as a development server - `setsid` starts the command in a new process group. Stopping the group also stops the processes the command starts, such as the `node` process behind `npm run dev`, unless a process moves to its own group. The script writes the exit code to a temporary file and then renames it, so a reader never sees a partial file. Do not start the command with `&` instead: a shell starts background jobs with `SIGINT` ignored. + `setsid` starts the command in a new process group, a set of processes that one `kill` can signal together. Stopping the group also stops the processes the command starts, such as the `node` process behind `npm run dev`, unless a process moves to its own group. The script writes the exit code to a temporary file and then renames it, so a reader never sees a partial file. Do not start the command with `&` instead: a shell starts background jobs with `SIGINT` ignored. - Each process keeps its files in its own directory under `/var/lib/processes` on the instance disk. The logs grow until the process ends. When the disk fills, writes from the process fail, so delete old process directories, or send output that you want to keep to a [mounted bucket](/sandbox/files/mount-an-r2-bucket/). + Each process keeps its files in its own directory under `/var/lib/processes` on the container disk. The logs grow until the process ends. When the disk fills, writes from the process fail, so delete old process directories, or send output that you want to keep to a [mounted bucket](/sandbox/files/mount-an-r2-bucket/). -2. Add a constructor to your Durable Object. It sets the inactivity timeout again when a restarted Durable Object finds the container running: +2. Add a constructor to your Durable Object. When the Durable Object [leaves memory and starts again](/sandbox/concepts/lifetime/#the-timeout-does-not-survive-a-restart), the constructor sets the inactivity timeout again if the container is still running: ```ts title="src/index.ts" export class MyContainer extends DurableObject { @@ -189,7 +189,7 @@ For a process that runs until the container stops, such as a development server `kill -0` checks that the process exists without sending it a signal. A process is `lost` when it ended without recording an exit code, for example because something sent `SIGKILL` to the script that records it. - The process ID file also records the boot ID, which changes in every instance. A [snapshot](/sandbox/files/save-and-restore-a-workspace/) restores the file, and a new instance reuses the same process IDs, so a restored process ID can belong to an unrelated process. `current()` ignores a process ID from another instance, so a process that was running when the snapshot was taken is `lost`. + The process ID file also records the boot ID, which changes each time a container starts. `current()` ignores a process ID from an earlier container, because a [snapshot](/sandbox/files/save-and-restore-a-workspace/) can restore the file, and the restored process ID can belong to an unrelated process. A process that was running when the snapshot was taken is therefore `lost`. 4. Add a method that returns the output of a process so far: @@ -268,7 +268,7 @@ For a process that runs until the container stops, such as a development server } ``` - A running process does not keep the container running. The alarm checks every process each minute, and each alarm keeps the container running. A Durable Object that restarted between alarms sets the 10-minute inactivity timeout again in its constructor. The container stops 10 minutes after the alarm finds that the last process has ended. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#activity-keeps-an-instance-running). + A running process does not keep the container running. While any process runs, the alarm checks every process once a minute, and each alarm keeps the container running. After the last process ends, the inactivity timeout decides when the container stops. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#activity-keeps-an-instance-running). A Durable Object has one alarm. If your class already has an `alarm()` handler, merge the process checks into it, and set the alarm to the earliest time that any check needs. For more information, refer to [Alarms](/durable-objects/api/alarms/). @@ -330,7 +330,7 @@ For a process that runs until the container stops, such as a development server - This example command prints `ready`, then prints a line each second for five minutes. Replace it with your own command. The route accepts only process IDs that are safe in a path, because each ID becomes a directory name. + This example command prints `ready`, then prints a line each second for five minutes. Replace it with your own command. `startProcess()` passes no `cwd` or `env` to `exec()`, so the command receives only `PATH` from the environment of the image. To set a working directory or variables, add them to that `exec()` call. For more information, refer to [Set the process context](/containers/guides/execute-commands/#set-the-process-context). The route accepts only process IDs that are safe in a path, because each ID becomes a directory name. 8. Deploy your Worker: diff --git a/src/content/docs/sandbox/commands/run-python-code.mdx b/src/content/docs/sandbox/commands/run-python-code.mdx index ba92aecebf4..ae17ac3d211 100644 --- a/src/content/docs/sandbox/commands/run-python-code.mdx +++ b/src/content/docs/sandbox/commands/run-python-code.mdx @@ -20,7 +20,7 @@ import { WranglerConfig, } from "~/components"; -Run Python code in a sandbox and return its output. The image sets the Python version and the packages the code can import. Files that one run writes stay in the sandbox for the next run. +Run Python code in a sandbox and return its output. The image sets the Python version and the packages the code can import. Each run starts a new Python process. Files that one run writes stay for the next run while the container keeps running. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/). ## Prerequisites @@ -78,7 +78,7 @@ Run Python code in a sandbox and return its output. The image sets the Python ve -4. Add an inactivity timeout to your Durable Object. The constructor sets the timeout again when a restarted Durable Object finds the container running: +4. Add an inactivity timeout to your Durable Object. When the Durable Object [leaves memory and starts again](/sandbox/concepts/lifetime/#the-timeout-does-not-survive-a-restart), the constructor sets the timeout again if the container is still running: ```ts title="src/index.ts" const INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000; @@ -128,7 +128,7 @@ Run Python code in a sandbox and return its output. The image sets the Python ve ], { stdin: new Response(code).body!, - // Files that the code writes here stay until the instance stops + // Files that the code writes here stay until the container stops cwd: "/workspace", }, ); @@ -236,7 +236,11 @@ To return an image, such as a `matplotlib` chart, have the code save it in `/wor } ``` -3. Run code that saves a chart, then open `/sandboxes/ada/chart.png` in your browser: +3. Deploy your Worker again: + + + +4. Run code that saves a chart, then open `/sandboxes/ada/chart.png` in your browser: ```python title="chart.py" import matplotlib @@ -264,4 +268,4 @@ To run another language, change the base image and the command that reads standa - [Build an AI code interpreter](/sandbox/get-started/build-an-ai-code-interpreter/): run code that a model writes in a Dynamic Worker. - [Stream command output](/sandbox/commands/stream-command-output/): show output while long code runs. -- [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/): keep files in `/workspace` after the instance stops. +- [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/): keep files in `/workspace` after the container stops. diff --git a/src/content/docs/sandbox/commands/run-tests-from-a-git-repository.mdx b/src/content/docs/sandbox/commands/run-tests-from-a-git-repository.mdx index d895ff376d2..02a57e9c70b 100644 --- a/src/content/docs/sandbox/commands/run-tests-from-a-git-repository.mdx +++ b/src/content/docs/sandbox/commands/run-tests-from-a-git-repository.mdx @@ -20,7 +20,7 @@ import { WranglerConfig, } from "~/components"; -Run the tests of a repository in a fresh sandbox. In this example, the Durable Object clones the repository, installs its dependencies, runs its test script, returns the result, and stops the instance. +Run the tests of a repository in a fresh sandbox. In this example, the Durable Object clones the repository, installs its dependencies, runs its test script, returns the result, and stops the container. ## Prerequisites @@ -141,7 +141,7 @@ Run the tests of a repository in a fresh sandbox. In this example, the Durable O `--` stops `git` from reading a repository value that starts with `-` as an option. - A step that runs longer than 10 minutes returns exit code `124`, or `137` if `timeout` also sends `SIGKILL`. `timeout` stops the step together with the processes it starts that stay in its process group. For more information, refer to [Stop the processes a command starts](/containers/guides/execute-commands/#stop-the-processes-a-command-starts). The `finally` block stops the instance after the run. + A step that runs longer than 10 minutes returns exit code `124`, or `137` if `timeout` also sends `SIGKILL`. `timeout` stops the step together with the processes it starts that stay in its process group. For more information, refer to [Stop the processes a command starts](/containers/guides/execute-commands/#stop-the-processes-a-command-starts). The `finally` block stops the container after the run. `output()` holds the whole output of a step in the memory of the Durable Object. For test suites that print a lot, [stream the output](/sandbox/commands/stream-command-output/) instead. @@ -173,7 +173,7 @@ Run the tests of a repository in a fresh sandbox. In this example, the Durable O - A random sandbox name gives each run a new instance, so one run cannot see files from another. + A random sandbox name reaches a new Durable Object, which starts a new container for this run, so one run cannot see files from another. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/). In this example, the Worker runs the tests of version `v0.0.6` of the public [`workers-oauth-provider`](https://github.com/cloudflare/workers-oauth-provider) repository. Replace the repository, revision, and `npm` commands with your own. `exec()` does not start a shell, so keep each argument in its own array entry. diff --git a/src/content/docs/sandbox/commands/stream-command-output.mdx b/src/content/docs/sandbox/commands/stream-command-output.mdx index d7f37aaa1c5..7ddaf1bf22d 100644 --- a/src/content/docs/sandbox/commands/stream-command-output.mdx +++ b/src/content/docs/sandbox/commands/stream-command-output.mdx @@ -206,7 +206,7 @@ events.addEventListener("exit", (event) => { A disconnect signals only the process that `exec()` started. Processes that the command starts, such as the commands in an `sh -c` script, keep the output streams open until they exit. To stop a command and the processes it starts after a time limit, run it under GNU coreutils `timeout`: -```ts +```ts title="src/index.ts" const build = ["timeout", "--kill-after=5", "60", "sh", "-c", "npm test"]; ``` diff --git a/src/content/docs/sandbox/concepts/index.mdx b/src/content/docs/sandbox/concepts/index.mdx index 7bf0f699564..766f04799a8 100644 --- a/src/content/docs/sandbox/concepts/index.mdx +++ b/src/content/docs/sandbox/concepts/index.mdx @@ -24,7 +24,7 @@ A sandbox runs code that you did not write and do not trust, such as code that a Code in a Container can use everything in its Linux image: runtimes, packages, files, and the processes it starts. -Code in a Dynamic Worker can use the Workers runtime APIs, plus the modules, methods, and values that your Worker passes to it. It does not inherit the bindings, credentials, or data of your Worker. It can reach the Internet unless you set `globalOutbound` to `null`. +Code in a Dynamic Worker can use the Workers runtime APIs, plus the modules, methods, and values that your Worker passes to it. It does not inherit the bindings, which give your Worker access to storage and other services, or the credentials or data of your Worker. The code can reach the Internet unless your Worker blocks outbound requests. For more information, refer to [Egress control](/dynamic-workers/usage/egress-control/). @@ -48,13 +48,13 @@ For example, your Worker can pass a `listPullRequests()` method that works for o [Code Mode](/agents/tools/codemode/) uses this for agents. The model writes JavaScript against typed methods, a Dynamic Worker runs it, and only the result returns to the model context. -`load()` creates a new Dynamic Worker. `get()` with a stable ID lets the runtime reuse a warm Dynamic Worker when the same code runs again. To give a generated application its own storage, use [Durable Object facets](/dynamic-workers/usage/durable-object-facets/). That storage does not give the application access to your other resources. +A generated application can also receive its own persistent storage without receiving access to your other resources. For more information, refer to [Durable Object facets](/dynamic-workers/usage/durable-object-facets/). ## How each environment isolates code -A Container runs in a [Firecracker](/containers/concepts/architecture/#container-runtime) microVM with its own kernel and network, which no other workload shares. Your image runs as a Linux container inside that VM. +A Container runs in a [Firecracker](/containers/concepts/architecture/#container-runtime) microVM, a lightweight virtual machine with its own Linux kernel and network, which no other workload shares. The kernel is the core of the operating system that runs processes and controls access to hardware. Your image runs as a Linux container inside that virtual machine. -V8, the JavaScript engine in Chrome, runs each Dynamic Worker in a sandbox that cannot read memory outside itself. The Workers runtime adds a process-level sandbox around it. For more information, refer to the [Workers security model](/workers/reference/security-model/). +V8, the JavaScript engine in Chrome, runs each Dynamic Worker in a sandbox that cannot read memory outside itself. The Workers runtime also restricts the operating system process that runs this code, so the process cannot reach the filesystem or the network directly. For more information, refer to the [Workers security model](/workers/reference/security-model/). ## What you maintain diff --git a/src/content/docs/sandbox/concepts/lifetime.mdx b/src/content/docs/sandbox/concepts/lifetime.mdx index d7492c4d5ae..9a5d1259697 100644 --- a/src/content/docs/sandbox/concepts/lifetime.mdx +++ b/src/content/docs/sandbox/concepts/lifetime.mdx @@ -17,7 +17,7 @@ import { SandboxLifetime } from "~/components/react/sandbox/SandboxLifetime"; A Linux sandbox is a [Durable Object](/durable-objects/) that starts and stops a [container](/containers/). Your Worker reaches the Durable Object by name, for example a session ID, and the same name always reaches the same Durable Object. When work arrives, the Durable Object calls [`this.ctx.container.start()`](/containers/api/durable-object-container/#start), which starts a container instance: a Linux virtual machine that runs your image, with its own processes and its own disk. -The Durable Object and the instance last for different lengths of time. The Durable Object keeps its storage, such as session details or a snapshot ID, until your code deletes it. An instance runs only until something stops it, and its processes and the files on its disk end with it. The next `start()` creates a new instance. +The Durable Object and the instance last for different lengths of time. The Durable Object keeps its storage until your code deletes it. That storage can hold session details, or the ID of a snapshot, which is a saved copy of the files on the disk of an instance. An instance runs only until something stops it, and its processes and the files on its disk end with it. The next `start()` creates a new instance. This page follows a coding agent. The agent clones a repository, installs its dependencies, edits files, and starts a development server. Then it waits an hour for its next instruction and reaches the same sandbox by name. The files are still there if the instance kept running or the application saved a snapshot. The development server is still there only if the instance kept running. @@ -29,9 +29,9 @@ The sandbox of the coding agent, from top to bottom: the name, the Durable Objec ## Activity keeps an instance running -An instance keeps running while its Durable Object is active. The Durable Object is active while it handles a request, runs an alarm, streams a response, or holds open a WebSocket that it accepted with `accept()`. The [browser terminal](/sandbox/commands/open-a-terminal-in-the-browser/) keeps a sandbox running this way, even when nobody types. +An instance keeps running while its Durable Object is active. The Durable Object is active while it handles a request, runs an alarm, streams a response, or holds open a WebSocket that it accepted with `accept()`. An alarm is a call that the Durable Object schedules for itself at a set time. The [browser terminal](/sandbox/commands/open-a-terminal-in-the-browser/) keeps a sandbox running this way, even when nobody types. -After the Durable Object becomes inactive, the instance keeps running for the time set with [`setInactivityTimeout()`](/containers/api/durable-object-container/#setinactivitytimeout), up to 6 hours. A request that arrives within that time reaches the same instance, with its files and processes. Without a timeout, Cloudflare stops the instance shortly after the Durable Object becomes inactive. +After the Durable Object becomes inactive, the instance keeps running for the time set with [`setInactivityTimeout()`](/containers/api/durable-object-container/#setinactivitytimeout). A request that arrives within that time reaches the same instance, with its files and processes. Without a timeout, Cloudflare stops the instance shortly after the Durable Object becomes inactive. Code that runs inside the instance does not count as activity. If no requests arrive, the instance stops when the inactivity timeout ends, even while a build, a test suite, or an agent task still runs inside it. A Durable Object [alarm](/durable-objects/api/alarms/) that runs more often than the timeout and checks the work keeps the instance running until the work ends. For an alarm that runs every minute, refer to [Run background processes](/sandbox/commands/run-background-processes/). @@ -58,7 +58,7 @@ An instance stops when one of these happens: The main process is the entrypoint passed to `start()`, or the default command of the image. When it exits, the instance stops, and every command started with `exec()` ends with it. The default command of `cloudflare/debian-trixie` exits right away, so the sandbox examples start that image with `sleep infinity` as the entrypoint, or run a web server as the main process. -When the inactivity timeout ends, every process in the instance receives `SIGTERM`, and Cloudflare stops the instance shortly after, whether or not the processes exit. `destroy()` stops the instance at once, with no time for processes to clean up. Do not rely on a `SIGTERM` handler to finish work. Save the files you need before the instance stops, for example in a [snapshot](#snapshots-carry-files-to-the-next-instance). +When the inactivity timeout ends, every process in the instance receives `SIGTERM`, the Linux signal that asks a process to exit, and Cloudflare stops the instance shortly after, whether or not the processes exit. `destroy()` stops the instance at once, with no time for processes to clean up. Do not rely on a `SIGTERM` handler to finish work. Save the files you need before the instance stops, for example in a [snapshot](#snapshots-carry-files-to-the-next-instance). Cloudflare does not wake the Durable Object when its instance stops. A pending [`monitor()`](/containers/api/durable-object-container/#monitor) call reports a stop only while the Durable Object is in memory. The call keeps the Durable Object in memory for up to 15 minutes from when the call starts. On compatibility dates before `2026-10-01`, the Durable Object leaves memory after its usual idle period instead, unless the Worker sets the [`durable_object_io_tasks_prevent_eviction`](/workers/configuration/compatibility-flags/#durable-object-io-tasks-prevent-eviction) flag. The inactivity timeout starts only after the Durable Object leaves memory, so the instance keeps running past the timeout and the Durable Object incurs duration charges until then. When the Durable Object leaves memory, the call ends without reporting the stop that follows. @@ -86,15 +86,15 @@ Container snapshots are in public beta. The API and behavior may change. [`snapshotContainer()`](/containers/api/durable-object-container/#snapshotcontainer) saves the writable root filesystem of a running instance. It does not save separately mounted filesystems, memory, or processes. Starting with a snapshot creates a new instance with the saved files, and the new instance runs its entrypoint. The processes that were running do not continue. The new instance reuses the same process IDs, so a process ID saved in a file can belong to another process after a restore. Files in `/run` are in memory, and a snapshot does not save them. -Your application decides when to save a snapshot. It stores the snapshot, for example in Durable Object storage, and passes it to the next `start()`. Cloudflare does not restore a snapshot on its own. Changes made after the last snapshot exist only in the current instance, and an instance can stop without warning, so save a snapshot after work that you need to keep. +Your application decides when to save a snapshot. It stores the snapshot handle that `snapshotContainer()` returns, for example in Durable Object storage, and passes it to the next `start()`. Cloudflare does not restore a snapshot on its own. Changes made after the last snapshot exist only in the current instance, and an instance can stop without warning, so save a snapshot after work that you need to keep. -A snapshot expires 30 days after it is created or last restored. To keep files longer, copy them to a bucket. +Snapshots expire according to the [snapshot retention limit](/containers/platform/limits/#snapshot-limits). To keep files longer, copy them to a bucket. The repository, dependencies, and edits of the coding agent return from a snapshot. The development server does not, so the agent starts it again. To save and restore a workspace, refer to [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/). To save a sandbox each time it stops for inactivity, refer to [Save a sandbox automatically](/sandbox/files/save-a-sandbox-automatically/). ## Mounted buckets outlive every instance -Files in a mounted [R2](/r2/) bucket are objects in that bucket. They remain after every instance stops, and other sandboxes and Workers with access to the bucket can read them. A snapshot does not include mounted directories, and a mount ends with its instance, so each new instance mounts the bucket again. +Files in a mounted [R2](/r2/) bucket are objects in that bucket. They remain after every instance stops, and other sandboxes and Workers with access to the bucket can read them. A mount ends with its instance, so each new instance mounts the bucket again. Use a snapshot for files that belong to one continuing workspace. Use a bucket for files that other systems read, or that must outlive the workspace. To mount a bucket, refer to [Mount an R2 bucket](/sandbox/files/mount-an-r2-bucket/). diff --git a/src/content/docs/sandbox/concepts/security.mdx b/src/content/docs/sandbox/concepts/security.mdx index 29db3999017..31edccef517 100644 --- a/src/content/docs/sandbox/concepts/security.mdx +++ b/src/content/docs/sandbox/concepts/security.mdx @@ -16,15 +16,15 @@ reviewed: 2026-09-23 import { SandboxCredentials } from "~/components/react/sandbox/SandboxCredentials"; -A sandbox runs code you do not trust, such as code that a model generates, a user writes, or a dependency brings in. Cloudflare isolates the sandbox from your Worker and from other sandboxes. For more information about that isolation, refer to [Sandboxes on Cloudflare](/sandbox/). +A sandbox runs code you do not trust, such as code that a model generates, a user writes, or a dependency brings in. Cloudflare isolates the sandbox from your Worker and from other sandboxes. For more information about that isolation, refer to [Choose a sandbox environment](/sandbox/concepts/#how-each-environment-isolates-code). Isolation does not limit what the code does inside its own sandbox. Assume that the code uses everything it can reach there, including the files, credentials, and network access that your Worker provides. ## Everything in one sandbox is shared -Every process in a Container runs on the same Linux system. The processes can read the same files, see each other, and connect to the same `localhost` ports. Code loaded into one Dynamic Worker shares its memory in the same way. +Every process in a Container runs in the same Linux environment, the [container instance](/sandbox/concepts/lifetime/). The processes can read the same files, see each other, and connect to the servers that other processes run on `localhost`. Code loaded into one Dynamic Worker shares its memory in the same way. -A separate Linux user does not keep a command away from files. In a deployed sandbox, every process has the same Linux capabilities as `root`, so file permissions do not restrict it, and it can switch back to `root`. For more information, refer to the [`exec()` `user` option](/containers/api/durable-object-container/#exec). +A separate Linux user does not keep a command away from files. `root` is the privileged Linux user. In a deployed sandbox, every process has the Linux capabilities of `root`, even when it runs under another user ID, so file permissions do not restrict it, and it can switch back to `root`. For more information, refer to the [`exec()` `user` option](/containers/api/durable-object-container/#exec). The sandbox is therefore the smallest unit of trust. When two users share a sandbox, each user's code can read the other's work. A sandbox for each user, or for each job, keeps one user's code away from another user's files and credentials. diff --git a/src/content/docs/sandbox/files/back-up-a-directory-to-r2.mdx b/src/content/docs/sandbox/files/back-up-a-directory-to-r2.mdx index 255a5b1c74a..53724707f63 100644 --- a/src/content/docs/sandbox/files/back-up-a-directory-to-r2.mdx +++ b/src/content/docs/sandbox/files/back-up-a-directory-to-r2.mdx @@ -21,7 +21,9 @@ import { WranglerConfig, } from "~/components"; -The `DirectoryBackup` class from `@cloudflare/sandbox` saves one directory as one object in your R2 bucket and restores it as ordinary files. A backup restores after the instance stops or after you deploy a new image. R2 keeps each backup until you delete it. In this example, the directory is a project in `/workspace/project`. +The `DirectoryBackup` class from `@cloudflare/sandbox` saves one directory as one object in your R2 bucket and restores it as ordinary files. R2 keeps each backup until you delete it. A container instance, the Linux virtual machine that your Durable Object starts, loses its files when it stops. You can restore a backup into a later instance, including one that runs a different image. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#deploys-keep-instances-running). + +In this example, the directory is a project in `/workspace/project`. ## Prerequisites @@ -324,7 +326,7 @@ export class MyContainer extends DurableObject { An [object lifecycle rule](/r2/buckets/object-lifecycles/) that expires objects under `backups/` also deletes backups. The rule leaves their records, and restoring one of those backups throws `BACKUP_NOT_FOUND`. -A backup that fails does not leave an object. If the Durable Object restarts during a backup, the incomplete multipart upload stays in the bucket until the default lifecycle rule of the bucket aborts it after seven days. +A backup that fails does not leave an object. If the Durable Object restarts during a backup, the incomplete multipart upload stays in the bucket until an [object lifecycle rule](/r2/buckets/object-lifecycles/) of the bucket aborts it. ## Related resources diff --git a/src/content/docs/sandbox/files/index.mdx b/src/content/docs/sandbox/files/index.mdx index 4fe95934d53..c8471ae7737 100644 --- a/src/content/docs/sandbox/files/index.mdx +++ b/src/content/docs/sandbox/files/index.mdx @@ -15,18 +15,18 @@ products: The `Files` class from `@cloudflare/sandbox` moves files between your Worker and a running sandbox, and provides common file system operations. For more information, refer to [Move files in and out of a sandbox](/sandbox/files/manage-files/). -Each sandbox name maps to a Durable Object, which starts a Linux instance when needed. Stopping the instance deletes the files on its disk. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#snapshots-carry-files-to-the-next-instance). +Each sandbox name reaches a [Durable Object](/durable-objects/), which starts a container instance when needed: a Linux virtual machine that runs your image. Stopping the instance deletes the files on its disk. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#snapshots-carry-files-to-the-next-instance). ## Persistence You can keep files from a sandbox in several ways, from a snapshot of the root filesystem to one directory kept in R2: -| Goal | Guide | Trade-off | -| ----------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| Continue an agent workspace in a later session | [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/) | Saves the root filesystem, but not mounted directories | -| Keep a workspace without explicit save calls | [Save a sandbox automatically](/sandbox/files/save-a-sandbox-automatically/) | Same as snapshots, plus an alarm in your Durable Object | -| Keep a project across images, or beyond 30 days | [Back up a directory to R2](/sandbox/files/back-up-a-directory-to-r2/) | Saves one directory, and your Durable Object stores each backup record | -| Share job inputs and outputs with other systems | [Mount an R2 bucket](/sandbox/files/mount-an-r2-bucket/) | Renames, locks, and permissions do not work as they do on a local disk | +| Goal | Guide | Trade-off | +| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| Continue an agent workspace in a later session | [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/) | Saves the root filesystem, but not mounted directories | +| Keep a workspace without explicit save calls | [Save a sandbox automatically](/sandbox/files/save-a-sandbox-automatically/) | Same as snapshots, plus an alarm in your Durable Object | +| Keep a project across images, or past the [snapshot retention limit](/containers/platform/limits/#snapshot-limits) | [Back up a directory to R2](/sandbox/files/back-up-a-directory-to-r2/) | Saves one directory, and your Durable Object stores each backup record | +| Share job inputs and outputs with other systems | [Mount an R2 bucket](/sandbox/files/mount-an-r2-bucket/) | Renames, locks, and permissions do not work as they do on a local disk | ## Combine approaches diff --git a/src/content/docs/sandbox/files/manage-files.mdx b/src/content/docs/sandbox/files/manage-files.mdx index 31af7895eb5..f51e6f47673 100644 --- a/src/content/docs/sandbox/files/manage-files.mdx +++ b/src/content/docs/sandbox/files/manage-files.mdx @@ -23,7 +23,7 @@ import { This guide streams an uploaded file into a sandbox, compresses it with `gzip`, and streams the compressed file back to the caller. -The `Files` class from `@cloudflare/sandbox` reads and writes files in a running Linux instance, and [`exec()`](/containers/api/durable-object-container/#exec) runs the command. `Files` does not start the instance, so your Durable Object starts it before each file operation. +The `Files` class from `@cloudflare/sandbox` reads and writes files in a running [container instance](/sandbox/concepts/lifetime/), the Linux virtual machine that your Durable Object starts. [`exec()`](/containers/api/durable-object-container/#exec) runs the command. `Files` does not start the instance, so your Durable Object starts it before each file operation. ## Prerequisites @@ -77,7 +77,7 @@ The `Files` class from `@cloudflare/sandbox` reads and writes files in a running The `workspace` key names the image. Your code starts it as `container.images.workspace`. -4. To start an instance before each file operation, replace `src/index.ts` with a `MyContainer` class that has a `startSandbox()` method: +4. To start an instance before each file operation, replace the import and the `MyContainer` class in `src/index.ts` with a class that has a `startSandbox()` method. Keep the default export until step 6 replaces it: @@ -158,7 +158,7 @@ The `Files` class from `@cloudflare/sandbox` reads and writes files in a running A Linux failure, such as a full disk, throws `SandboxFileError`. Its `code` holds the Linux error name, such as `ENOSPC`. To handle other errors, refer to [Files errors](/sandbox/reference/files/#errors). -6. To send uploads to `compress()`, add a default export to `src/index.ts`: +6. To send uploads to `compress()`, replace the default export in `src/index.ts`: @@ -211,21 +211,11 @@ The `Files` class from `@cloudflare/sandbox` reads and writes files in a running -## List, rename, and remove files - -`Files` has methods for other file operations. Each method accepts the same `cwd` option as `writeFile()` and `readFile()`: - -| Method | Use it to | -| ----------------- | ---------------------------------------------------- | -| `readDirectory()` | List directory entries | -| `stat()` | Read the type, size, owner, and timestamps of a path | -| `mkdir()` | Create a directory | -| `rename()` | Rename or move a file or directory | -| `remove()` | Delete a file, or a directory with `recursive: true` | +## Remove output files Compressed files stay in `/workspace` until the instance stops. To delete a file sooner, call `remove()` after the caller has read the response. -For more information, refer to [Files API](/sandbox/reference/files/). +For other file operations, such as listing a directory or renaming a file, refer to [Files API](/sandbox/reference/files/). ## Accept paths from callers diff --git a/src/content/docs/sandbox/files/mount-an-r2-bucket.mdx b/src/content/docs/sandbox/files/mount-an-r2-bucket.mdx index 85881756bcd..c51a924b17d 100644 --- a/src/content/docs/sandbox/files/mount-an-r2-bucket.mdx +++ b/src/content/docs/sandbox/files/mount-an-r2-bucket.mdx @@ -21,7 +21,7 @@ import { WranglerConfig, } from "~/components"; -The `S3Mount` class from `@cloudflare/sandbox` mounts one prefix of a bucket as a directory with `s3fs`. Your Worker keeps the R2 credentials and signs each storage request. In this example, a command reads an input file from the mount and writes a digest next to it. +The `S3Mount` class from `@cloudflare/sandbox` mounts one prefix of a bucket as a directory. A prefix is the start of the object keys, such as `jobs/ada`. In the sandbox, `s3fs` presents the objects under the prefix as files through FUSE, the Linux interface that lets a process provide a filesystem. Your Worker keeps the R2 credentials and signs each storage request. In this example, a command reads an input file from the mount and writes a digest next to it. ## Prerequisites @@ -38,7 +38,7 @@ The `S3Mount` class from `@cloudflare/sandbox` mounts one prefix of a bucket as -2. Create a `Dockerfile` in your project root with FUSE, `s3fs`, and the `sandbox-shim` binary that `S3Mount` uses: +2. Create a `Dockerfile` in your project root. It installs FUSE and `s3fs`, and copies `sandbox-shim`, the helper binary that `S3Mount` runs in the container: ```dockerfile title="Dockerfile" FROM node:24-trixie-slim diff --git a/src/content/docs/sandbox/files/save-a-sandbox-automatically.mdx b/src/content/docs/sandbox/files/save-a-sandbox-automatically.mdx index 4e8d441f95f..c0029777df8 100644 --- a/src/content/docs/sandbox/files/save-a-sandbox-automatically.mdx +++ b/src/content/docs/sandbox/files/save-a-sandbox-automatically.mdx @@ -16,7 +16,7 @@ reviewed: 2026-09-24 import { PackageManagers, Steps } from "~/components"; -An alarm in the Durable Object saves a Container snapshot of an idle sandbox and stops it. While the sandbox is in use, the alarm also saves checkpoints. The next request starts the sandbox from the last snapshot. +An [alarm](/durable-objects/api/alarms/), a call that the Durable Object schedules for itself, saves a Container snapshot of an idle sandbox and stops its instance. The instance is the Linux virtual machine that runs your image, and a snapshot is a saved copy of the files on its disk. While the sandbox is in use, the alarm also saves checkpoints. The next request starts a new instance from the last snapshot. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/). :::note[Public beta] Container snapshots are in public beta. Features and behavior may change. @@ -98,7 +98,7 @@ Container snapshots are in public beta. Features and behavior may change. } ``` - `savedAt` holds when the files were last saved or restored. `busy` counts the running commands, and the Durable Object does not save or stop a sandbox while a command runs. A request in progress keeps the Durable Object in memory, so `busy` does not need storage. If the stored snapshot cannot be restored, `start()` fails. To handle that failure, refer to [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/). + `savedAt` holds when the files were last saved or restored. `busy` counts the running commands, and the alarm starts a save only when no command is running. A request in progress keeps the Durable Object in memory, so `busy` does not need storage. If the stored snapshot cannot be restored, `start()` fails. To handle that failure, refer to [Save and restore a sandbox with snapshots](/sandbox/files/save-and-restore-a-workspace/). 3. Add a method that saves a snapshot, and an alarm handler that saves and stops an idle sandbox and saves a checkpoint of a sandbox in use: @@ -156,9 +156,9 @@ Container snapshots are in public beta. Features and behavior may change. A Durable Object has one alarm. If your class already has an `alarm()` handler, merge the idle and checkpoint checks into it, and set the alarm to the earliest time that any check needs. For more information, refer to [Alarms](/durable-objects/api/alarms/). - A snapshot saves the writable root filesystem. It does not include mounted directories, memory, or running processes. A background process that writes a file during a save can leave part of that file in the snapshot. + A background process that writes a file during a save can leave part of that file in the snapshot. To save a consistent checkpoint, finish background writes before the save. For what a snapshot leaves out, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#snapshots-carry-files-to-the-next-instance). - Each save creates a new snapshot. The Durable Object container API has no method to delete a snapshot. A snapshot expires 30 days after it is created or last restored. To save fewer snapshots, increase `CHECKPOINT_INTERVAL_MS` or remove checkpoints. + Each save creates a new snapshot. The Durable Object container API has no method to delete a snapshot. Snapshots expire according to the [snapshot retention limit](/containers/platform/limits/#snapshot-limits). To save fewer snapshots, increase `CHECKPOINT_INTERVAL_MS` or remove checkpoints. 4. Add a method that returns the state of the sandbox: diff --git a/src/content/docs/sandbox/files/save-and-restore-a-workspace.mdx b/src/content/docs/sandbox/files/save-and-restore-a-workspace.mdx index 0da332dc293..69b12ce1cf1 100644 --- a/src/content/docs/sandbox/files/save-and-restore-a-workspace.mdx +++ b/src/content/docs/sandbox/files/save-and-restore-a-workspace.mdx @@ -17,7 +17,7 @@ reviewed: 2026-09-24 import { PackageManagers, Steps, TypeScriptExample } from "~/components"; -A Durable Object saves the filesystem of a sandbox as a Container snapshot, stores the snapshot ID, and starts the next instance from the snapshot. In this example, the sandbox keeps a notes file in `/workspace`. +A Durable Object saves the filesystem of a sandbox as a Container snapshot, stores the snapshot ID, and starts the next instance from the snapshot. An instance is the Linux virtual machine that runs your image, and a snapshot is a saved copy of the files on its disk. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/). In this example, the sandbox keeps a notes file in `/workspace`. :::note[Public beta] Container snapshots are in public beta. Features and behavior may change. @@ -31,7 +31,7 @@ Container snapshots are in public beta. Features and behavior may change. -1. Add an inactivity timeout to your Durable Object. The constructor sets the timeout again when a restarted Durable Object finds the container running: +1. Add an inactivity timeout to your Durable Object. A Durable Object [restarts](/sandbox/concepts/lifetime/#the-timeout-does-not-survive-a-restart) when it leaves memory and runs its constructor again, and its instance can keep running. The constructor sets the timeout again when it finds the container running: ```ts title="src/index.ts" const INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000; @@ -82,6 +82,8 @@ Container snapshots are in public beta. Features and behavior may change. If the stored snapshot cannot be restored, the request fails with an error such as `Snapshot "" was not found.` Later requests fail the same way until the stored ID changes. The method does not fall back to the image, because the sandbox would then start with an empty workspace. + A snapshot is tied to the image it was saved from, and it cannot be applied to a different image. To carry files to a new image, [back up a directory to R2](/sandbox/files/back-up-a-directory-to-r2/) instead. For more information, refer to [Container snapshots](/containers/guides/snapshots/#create-a-container-snapshot). + 2. Add a method that runs one operation at a time, and a method that saves the filesystem and stops the instance: ```ts title="src/index.ts" @@ -113,7 +115,7 @@ Container snapshots are in public beta. Features and behavior may change. A command that writes a file while the snapshot runs can succeed and then disappear when `destroy()` stops the instance. `exclusive()` makes every operation that touches the disk wait for the save, and the save wait for them. Run each such method through it. - A snapshot saves the writable root filesystem. It does not include mounted directories, memory, or running processes. Finish writing files before you save. For more information about what a restored instance starts with, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#snapshots-carry-files-to-the-next-instance). + Finish writing files before you save. For what a snapshot saves and what a restored instance starts with, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#snapshots-carry-files-to-the-next-instance). 3. Add a method that adds a line to a file and returns the file, so you can check what a restore brings back: diff --git a/src/content/docs/sandbox/get-started/build-a-coding-agent-runner.mdx b/src/content/docs/sandbox/get-started/build-a-coding-agent-runner.mdx index a76496deafa..1850fb4dcf4 100644 --- a/src/content/docs/sandbox/get-started/build-a-coding-agent-runner.mdx +++ b/src/content/docs/sandbox/get-started/build-a-coding-agent-runner.mdx @@ -54,7 +54,8 @@ You will learn how to: You also need: -- An [authenticated AI Gateway](/ai-gateway/configuration/authentication/) and a gateway token with the **Run** permission. +- An account on the Workers Paid plan. Containers are available only on that plan. Refer to [Containers pricing](/containers/platform/pricing/). +- An [authenticated AI Gateway](/ai-gateway/configuration/authentication/) and a gateway token with the **Run** permission. Save the token and the gateway ID, which follows your account ID in the gateway URL: `https://gateway.ai.cloudflare.com/v1///`. - Anthropic credentials in AI Gateway, either [Unified Billing](/ai-gateway/features/unified-billing/) credits or an Anthropic API key stored as a [provider key](/ai-gateway/configuration/bring-your-own-keys/). - Your [Cloudflare account ID](/fundamentals/account/find-account-and-zone-ids/). @@ -78,9 +79,9 @@ You also need: 3. Install the [`@cloudflare/sandbox`](/sandbox/reference/) package and [Zod](https://zod.dev/): - + -4. Create a `Dockerfile` in your project root: +4. Create a `Dockerfile` in your project root. Wrangler builds it into an image, which supplies the files and installed tools of the container: ```dockerfile title="Dockerfile" FROM node:24-trixie-slim @@ -101,9 +102,9 @@ You also need: Install everything the agent needs in the image, because the sandbox cannot download packages at run time. - Pin the Claude Code version. Its command-line flags and event format can change between releases. Use the `cloudflare/sandbox` tag that matches the `@cloudflare/sandbox` version you installed. + Pin the Claude Code version. Its command-line flags and event format can change between releases. The `cloudflare/sandbox` tag matches the `@cloudflare/sandbox` version you installed. If you change one version, change the other to match. -5. Replace `wrangler.jsonc`. Replace `` with your account ID, and `default` with your gateway ID if it is different: +5. Replace `wrangler.jsonc`. It configures a [Durable Object](/durable-objects/), a named object that handles requests, keeps its own storage, and starts the container. Replace `` with your account ID, and `default` with your gateway ID if it is different: @@ -179,7 +180,7 @@ In the next three sections, you create `src/outbound.ts` and `src/sandbox.ts` an ## 2. Allow only the gateway and GitHub -Create `src/outbound.ts`. The sandbox sends every HTTP request on port `80` and HTTPS request on port `443` through this entrypoint in your Worker. Without Internet access, the sandbox cannot reach other ports: +Create `src/outbound.ts`. The sandbox sends every HTTP request on port `80` and HTTPS request on port `443` through the `Outbound` entrypoint, a class in your Worker whose `fetch()` method receives each intercepted request. Without Internet access, the sandbox cannot reach other ports: @@ -238,6 +239,13 @@ To allow another destination, add it to this entrypoint. Each destination you ad ## 3. Run Claude Code in the sandbox +The code in this section uses these terms. The sections after the code explain how the tutorial uses each one: + +- A certificate authority (CA) signs HTTPS certificates. `caPath` is the certificate of the Cloudflare CA that Containers uses to re-sign HTTPS requests from the container, so that `Outbound` can read them. `trustEnv` tells tools in the container to trust it. +- A process group is a set of processes that receive signals together. `taskScript` starts Claude Code in its own process group. +- An [alarm](/durable-objects/api/alarms/) is a scheduled call to the `alarm()` method of a Durable Object. +- An [instance type](/containers/platform/limits/#instance-types) sets the vCPU, memory, and disk of a container. This tutorial uses `standard-1`, which has more memory and disk than the default `lite` type. + Create `src/sandbox.ts`. The `AgentSandbox` Durable Object clones a repository, runs one Claude Code task at a time, and reports the task state and the repository diff: @@ -658,7 +666,7 @@ Claude Code exits successfully even when a model request fails. The outcome come While no exit code exists, the Durable Object runs `kill -0` to check whether the process still exists. The command runs through `sh`, because `kill` is a shell built-in and the slim image has no separate `kill` binary. If the process is gone, the Durable Object reads the exit code again, because the task can finish between the two reads. Only a process that ended without an exit code is `lost`. -The process ID file also records the boot ID, which changes in every instance. If you restore the workspace from a snapshot, the file comes back, and the new instance reuses the same process IDs. The check ignores a process ID from another instance, so a task that was running when the snapshot was taken is `lost`. +The process ID file also records the boot ID, which changes in every instance. A new instance can reuse the same process IDs, so the check ignores a process ID that another instance recorded. `readDiff()` marks new files with `git add --intent-to-add` so that the diff includes them. `git diff HEAD` compares the files with the checked-out commit, so the diff also includes changes that the agent staged. @@ -825,7 +833,7 @@ For more information, refer to [Sandbox security](/sandbox/concepts/security/#th -1. Clone GitHub's `Hello-World` repository into a sandbox named `agent-1`: +1. Clone the `octocat/Hello-World` repository into a sandbox named `agent-1`: ```sh curl "$WORKER_URL/sandboxes/agent-1/repository" \ @@ -842,6 +850,8 @@ For more information, refer to [Sandbox security](/sandbox/concepts/security/#th } ``` + Clone a repository once per sandbox. A second clone into `agent-1` fails while its container runs, because `/workspace/repo` already exists. To start over, use a new sandbox name, such as `agent-2`, in every request in these steps. + 2. Start a task: ```sh @@ -855,7 +865,7 @@ For more information, refer to [Sandbox security](/sandbox/concepts/security/#th { "state": "running" } ``` -3. Check the task until its state is `succeeded` or `failed`: +3. Check the task until its state is no longer `running`: ```sh curl "$WORKER_URL/sandboxes/agent-1/task" @@ -872,6 +882,8 @@ For more information, refer to [Sandbox security](/sandbox/concepts/security/#th The reply differs on each run. This task takes a few seconds, and larger tasks take minutes. The alarm keeps the sandbox running until Claude Code exits, so you do not need to keep checking. + `lost` means that the process or its container stopped before Claude Code recorded an exit code. `none` means that the container has no task, for example because it stopped after the task ended. Neither state changes on later checks. To try again, start over with a new sandbox name. + 4. Read the changes: ```sh @@ -897,6 +909,30 @@ If the model request fails, the task state is `failed` and `error` contains the - `Failed to authenticate. API Error: 401 Unauthorized` means the gateway rejected the token. Check that the token belongs to the account and gateway in `wrangler.jsonc`. Claude Code retries a failing model request 10 times, so this error takes about three minutes to appear. - `API Error: 402 Insufficient wholesale credits` means the gateway uses Unified Billing and the account has no credits. Add credits, or store an Anthropic API key in the gateway. +## 7. Clean up + +If you do not plan to add authentication, delete the Container application and the Worker. + + + +1. List your Container applications and copy the ID of the application for this Worker: + + + +2. Delete the application and its container instances: + + + +3. Delete the Worker: + + + + + ## Next steps - Run another agent in the same Worker. Refer to [Codex](/sandbox/coding-agents/codex/), [OpenCode](/sandbox/coding-agents/opencode/), or [Pi](/sandbox/coding-agents/pi/). diff --git a/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx b/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx index 2688cc57b1d..8d9180c31e7 100644 --- a/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx +++ b/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx @@ -47,6 +47,8 @@ You will learn how to: +To deploy this project, use an account on the Workers Paid plan. Dynamic Workers are available only on that plan. Refer to [Dynamic Workers pricing](/dynamic-workers/pricing/). + ## 1. Create the project @@ -69,7 +71,9 @@ You will learn how to: -4. Replace `wrangler.jsonc` to add a Workers AI binding and a Worker Loader binding: + The AI SDK sends requests to the model and handles its replies. Zod checks the shape of the request body and of the input that the model sends. + +4. Replace `wrangler.jsonc` to add a Workers AI binding and a Worker Loader binding. A binding gives your Worker access to an API through `env`: @@ -186,6 +190,8 @@ The generated code becomes the body of an async function, so it can use `await` ## 3. Let the model run code +A tool is a function that the model can ask your Worker to call. Your Worker runs the function and sends the result back to the model, so the model never calls the `LOADER` binding itself. + Replace `src/index.ts`. The Worker gives the model a `runJavaScript` tool and returns the answer from the model with every piece of code the model ran: @@ -287,7 +293,7 @@ The Worker validates the request body with Zod before it calls the model. If a W During `wrangler dev`, the Dynamic Worker runs locally. Workers AI requests always run on Cloudflare and count toward your [Workers AI usage](/workers-ai/platform/pricing/). -2. POST a question to the URL Wrangler prints. The default is `http://localhost:8787`: +2. Leave the development server running. Open a second terminal and POST a question to the URL Wrangler prints. The default is `http://localhost:8787`: ```sh curl http://localhost:8787 --json '{"question":"How many prime numbers are there below 100,000?"}' @@ -307,7 +313,9 @@ The Worker validates the request body with Zod before it calls the model. If a W } ``` - The model writes different code and wording each time. `result` is `9592`. + The model writes different code and wording each time. For a successful run, check that `result` in the last entry of `runs` is `9592`. + + If `answer` is empty, check `runs` for errors and send the request again. The Worker stops the model after five steps, which can end the conversation before the model answers. If the response status is `502`, read the logged error in the terminal that runs `wrangler dev`, fix its cause, and send the same request again. 3. Ask a question that needs sorting: @@ -315,7 +323,7 @@ The Worker validates the request body with Zod before it calls the model. If a W curl http://localhost:8787 --json '{"question":"Sort these words by length, then alphabetically: pear, fig, banana, kiwi, apple, date."}' ``` - The answer lists `fig, date, kiwi, pear, apple, banana`. + Check that the answer orders the words as `fig, date, kiwi, pear, apple, banana`. @@ -342,6 +350,12 @@ For more information, refer to [Sandbox security](/sandbox/concepts/security/). +## 6. Clean up + +If you do not plan to add authentication, delete the Worker: + + + ## Next steps - Let generated code call methods that your Worker provides. Refer to [Bindings](/dynamic-workers/usage/bindings/). diff --git a/src/content/docs/sandbox/get-started/dynamic-workers.mdx b/src/content/docs/sandbox/get-started/dynamic-workers.mdx index 5a75fbb964c..ef2b96a5396 100644 --- a/src/content/docs/sandbox/get-started/dynamic-workers.mdx +++ b/src/content/docs/sandbox/get-started/dynamic-workers.mdx @@ -19,7 +19,7 @@ import { WranglerConfig, } from "~/components"; -You will POST JavaScript to a Worker and read `{"result":["Ada"]}`. +You will send JavaScript in the body of an HTTP POST request to a [Worker](/workers/). The Worker runs the code and returns `{"result":["Ada"]}`. ## Prerequisites @@ -34,7 +34,7 @@ You will POST JavaScript to a Worker and read `{"result":["Ada"]}`. 2. Change into the project directory: @@ -43,7 +43,7 @@ You will POST JavaScript to a Worker and read `{"result":["Ada"]}`. cd sandbox-dynamic-worker ``` -3. Replace `wrangler.jsonc` to add a Worker Loader binding: +3. Replace `wrangler.jsonc` to add a Worker Loader binding. A binding gives your Worker access to an API through `env`. The `LOADER` binding creates new Workers at runtime, called [Dynamic Workers](/dynamic-workers/): @@ -71,7 +71,7 @@ You will POST JavaScript to a Worker and read `{"result":["Ada"]}`. -5. Replace `src/index.ts`. Your Worker reads `code` from the JSON body and runs it in the sandbox: +5. Replace `src/index.ts`. Your Worker reads `code` from the JSON body and places it in the `evaluate()` method of a `Code` class in the `code.js` module. `load()` creates a Dynamic Worker from that module. Calling `evaluate()` through `getEntrypoint("Code")` runs the code in that Dynamic Worker: @@ -122,7 +122,7 @@ You will POST JavaScript to a Worker and read `{"result":["Ada"]}`. -7. POST JavaScript to the URL Wrangler prints. The default is `http://localhost:8787`: +7. Leave the development server running. Open a second terminal and POST JavaScript to the URL Wrangler prints. The default is `http://localhost:8787`: ```sh curl http://localhost:8787 --request POST --json '{ diff --git a/src/content/docs/sandbox/get-started/index.mdx b/src/content/docs/sandbox/get-started/index.mdx index 67393a035ee..30bd10d9fca 100644 --- a/src/content/docs/sandbox/get-started/index.mdx +++ b/src/content/docs/sandbox/get-started/index.mdx @@ -23,7 +23,7 @@ import { WranglerConfig, } from "~/components"; -You will POST `uname -a` to a Worker and read stdout that contains `Linux`. +You will send `uname -a` in the body of an HTTP POST request to a [Worker](/workers/). The Worker runs the command in Linux and returns its standard output (stdout), which contains `Linux`. ## Prerequisites @@ -47,7 +47,7 @@ You will POST `uname -a` to a Worker and read stdout that contains `Linux`. cd sandbox-linux ``` -3. Replace `wrangler.jsonc` so a Durable Object can start a container: +3. Replace `wrangler.jsonc` so a Durable Object can start a container. A [Durable Object](/durable-objects/) is a named object that handles requests and keeps its own storage. In this project, the `MyContainer` Durable Object starts the container and runs your commands: @@ -86,6 +86,8 @@ You will POST `uname -a` to a Worker and read stdout that contains `Linux`. + `scheduling_policy: "durable_object"` lets the Durable Object choose the image and options each time it starts the container. Refer to [Scheduling policies](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy). + 4. Replace `src/index.ts`. The Worker reads `argv` from the JSON body and runs it in Linux: @@ -131,17 +133,27 @@ You will POST `uname -a` to a Worker and read stdout that contains `Linux`. + `start()` starts an instance of the container from the `cloudflare/debian-trixie` image. The `entrypoint` is the command that the instance runs when it starts. `sleep infinity` never exits, so the instance keeps running and accepts commands from `exec()`. + 5. Generate types for the binding. Wrangler reads the `MyContainer` class from `src/index.ts` to type `env.MY_CONTAINER`: -6. Run `wrangler dev`: +6. Start Docker. `wrangler dev` runs the instance in Docker on your machine. To install Docker, follow the [Docker Desktop installation docs](https://docs.docker.com/desktop/). Check that Docker is running: + + ```sh + docker info + ``` + + If Docker is not running, the command hangs or returns an error. + +7. Run `wrangler dev`: - `wrangler dev` runs the instance in [Docker](https://www.docker.com/) on your machine, so Docker must be running. Running `cloudflare/debian-trixie` locally needs Wrangler 4.141.0 or later. + Running `cloudflare/debian-trixie` locally needs Wrangler 4.141.0 or later. Refer to [Local development](/containers/guides/local-dev/). -7. POST a command to the URL Wrangler prints. The default is `http://localhost:8787`: +8. Leave the development server running. Open a second terminal and POST a command to the URL Wrangler prints. The default is `http://localhost:8787`: ```sh curl http://localhost:8787 --request POST --json '{"argv":["uname","-a"]}' @@ -153,8 +165,7 @@ The JSON body includes `"exitCode":0`. `stdout` contains `Linux`. The Worker sta ## Next steps -- Run another process in the instance. Refer to [`exec()`](/containers/api/durable-object-container/#exec). +- Run another command or change startup options. Refer to the [Durable Object container API](/containers/api/durable-object-container/). - Deploy your Worker. Refer to [Deploy Containers](/containers/guides/deploy/). A deploy does not replace a running instance. Refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#deploys-keep-instances-running). - Run JavaScript. Refer to [Run JavaScript](/sandbox/get-started/dynamic-workers/). -- Use the [Durable Object container API](/containers/api/durable-object-container/) for `start()` and `exec()`. - Start a project from the [minimal sandbox template](https://github.com/cloudflare/sandbox-sdk/tree/main/examples/minimal). It adds a `Dockerfile`, a sandbox for each name in the URL, and file reads and writes with `@cloudflare/sandbox`. diff --git a/src/content/docs/sandbox/index.mdx b/src/content/docs/sandbox/index.mdx index 660ccc954d5..5eab2a3680a 100644 --- a/src/content/docs/sandbox/index.mdx +++ b/src/content/docs/sandbox/index.mdx @@ -32,11 +32,11 @@ Start a sandbox and run untrusted or generated code -A sandbox is an isolated place to run code. The code cannot directly read the memory or data of your application. Your Worker decides which application APIs and data the code receives, and whether the code can reach the public Internet. +A sandbox is an isolated place to run code. The code cannot directly read the memory or data of your application. Your [Worker](/workers/), the application code that handles your traffic on Cloudflare, decides which application APIs and data the code receives, and whether the code can reach the public Internet. You can use a sandbox for agent-written code, user-uploaded applications, data analysis, development previews, build pipelines, or any other work that should not share a process with your application. -Cloudflare provides two sandbox environments. Both are accessible through a [Worker](/workers/), the application that already handles your traffic. +Cloudflare provides two sandbox environments. Both are accessible through your Worker. If your application uses `@cloudflare/sandbox` 0.x, refer to [Sandbox SDK 0.x](/sandbox/sdk/), or move it to the current version with [Migrate from Sandbox SDK 0.x](/sandbox/sdk/migrate/). @@ -44,15 +44,18 @@ If your application uses `@cloudflare/sandbox` 0.x, refer to [Sandbox SDK 0.x](/ ## Containers -[Containers](/containers/) run an image you provide. The instance is a full Linux environment, so you can run any language and keep processes running. Your Worker starts the instance and sends it work. HTTP from the Internet reaches the instance only through your Worker. +[Containers](/containers/) run an image you provide. An image holds the files, packages, and default command of a Linux environment. -Each instance is a microVM with its own kernel and network, so no other workload shares it. +Each Linux sandbox is a [Durable Object](/durable-objects/), an object with a unique name and its own storage. The Durable Object starts a container instance: a Linux virtual machine that runs your image. The instance is a full Linux environment, so you can run any language and keep processes running. Your Worker reaches the Durable Object by name and sends it work. HTTP from the Internet reaches the instance only through your Worker. For how the Durable Object and the instance relate, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/). + +No other workload shares an instance. For how each environment isolates code, refer to [Choose a sandbox environment](/sandbox/concepts/#how-each-environment-isolates-code). A sandbox container uses the [Durable Object scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy), which is in public beta. -A Durable Object in your Worker starts the instance from an image and runs a command in it: +Inside the Durable Object, `this.ctx.container` controls the instance. This excerpt starts the instance from an image and runs a command in it: ```ts +const container = this.ctx.container; container.start({ image: "cloudflare/debian-trixie", entrypoint: ["sleep", "infinity"], @@ -80,11 +83,11 @@ const output = await process.output(); The Workers runtime runs each Dynamic Worker apart from your Worker and from other Dynamic Workers. A Dynamic Worker reaches your application only through the methods and data you pass to it. -Your Worker loads the untrusted module as a new Worker, blocks its outbound requests, and calls it: +Your Worker loads the untrusted module as a new Worker, blocks its outbound requests, and calls it. In this excerpt, `env.LOADER` is the [Worker Loader binding](/dynamic-workers/getting-started/#configure-worker-loader) that creates Dynamic Workers, `untrustedModule` is a string that holds the untrusted code, and `CodeEntrypoint` is a TypeScript type for the `Code` class that the untrusted code exports: ```ts const sandbox = env.LOADER.load({ - compatibilityDate: "$today", + compatibilityDate: "2026-10-01", mainModule: "code.js", modules: { "code.js": untrustedModule }, globalOutbound: null, @@ -212,6 +215,8 @@ Run models on Cloudflare, then execute the code they generate in a sandbox. ## More resources +These resources cover deployment, reference, pricing, and related tools: + | Topic | Links | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Deploy and debug | [Deploy Containers](/containers/guides/deploy/), [Local development](/containers/guides/local-dev/), [Logs](/workers/observability/logs/workers-logs/) | diff --git a/src/content/docs/sandbox/manage/list-sandboxes.mdx b/src/content/docs/sandbox/manage/list-sandboxes.mdx index 06fd9a026fa..aa13db67846 100644 --- a/src/content/docs/sandbox/manage/list-sandboxes.mdx +++ b/src/content/docs/sandbox/manage/list-sandboxes.mdx @@ -61,7 +61,7 @@ Each user gets a registry, a Durable Object that records the sandboxes of that u -2. Add the registry class to your Worker. Each user gets one `SandboxDirectory`, which stores the user's sandbox sessions in SQLite: +2. Add the registry class to your Worker. Each user gets one `SandboxDirectory`, which stores a row in SQLite for each sandbox of that user. The row holds a sandbox name that is unique within that user, called `session` in this example. The full Durable Object name of the sandbox is `/`: ```ts title="src/index.ts" export class SandboxDirectory extends DurableObject { @@ -239,6 +239,7 @@ curl "https://api.cloudflare.com/client/v4/accounts//containers/appl - The application ID appears in `wrangler containers list`. The application is named after the Worker and the Durable Object class, such as `sandbox-linux-mycontainer`. - The [API token](/fundamentals/api/get-started/create-token/) needs the **Containers Read** permission, which covers every Container application in the account. Use it for administration and cleanup, not in routes that users call. - `name_prefix` filters by the start of the name, and `state=active` returns only sandboxes that are starting, running, or stopping. Stopped sandboxes stay in the list. +- A response holds one page of results. While a response has `result_info.next_page_token`, pass that value as `page_token` in the next request to get the next page. - A new sandbox can take a few seconds to appear, and sandboxes that run under `wrangler dev` do not appear. ## Related resources diff --git a/src/content/docs/sandbox/manage/run-code-when-a-sandbox-stops.mdx b/src/content/docs/sandbox/manage/run-code-when-a-sandbox-stops.mdx index 5e5e45fb83c..a323d9f2738 100644 --- a/src/content/docs/sandbox/manage/run-code-when-a-sandbox-stops.mdx +++ b/src/content/docs/sandbox/manage/run-code-when-a-sandbox-stops.mdx @@ -41,7 +41,7 @@ The runtime does not wake a Durable Object when its container stops. Your Durabl } ``` - Keep the inactivity timeout longer than the check interval, so the container keeps running between alarms. While `monitor()` waits, the inactivity timeout does not start, so the alarm enforces the idle limit. If the alarms stop, the timeout still stops the container after the Durable Object leaves memory. For more information, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#what-stops-an-instance). + Keep the inactivity timeout longer than the check interval, so the container keeps running between alarms. A pending `monitor()` call can delay the inactivity timeout, so the alarm enforces the idle limit. If the alarms stop, the timeout still stops the container after the Durable Object leaves memory. For how long `monitor()` keeps the Durable Object in memory, refer to [Sandbox lifetime](/sandbox/concepts/lifetime/#what-stops-an-instance). 2. In `MyContainer`, add a constructor, a method that watches the container, and a method that records the stop: diff --git a/src/content/docs/sandbox/network/call-an-authenticated-api.mdx b/src/content/docs/sandbox/network/call-an-authenticated-api.mdx index ae36e60569d..054388e30e1 100644 --- a/src/content/docs/sandbox/network/call-an-authenticated-api.mdx +++ b/src/content/docs/sandbox/network/call-an-authenticated-api.mdx @@ -21,7 +21,7 @@ import { WranglerConfig, } from "~/components"; -Let code in a sandbox call an API that needs a token, while the token stays in your Worker. Requests from the sandbox reach an entrypoint in your Worker, which allows only the requests you choose and adds the token. In this example, the code reads your profile from the GitHub API. +Let code in a sandbox call an API that needs a token, while the token stays in your Worker. Requests from the sandbox reach an entrypoint in your Worker, a class that the Worker exports to handle requests or method calls. The entrypoint allows only the requests you choose and adds the token. In this example, the code reads your profile from the GitHub API. ## Prerequisites @@ -56,7 +56,7 @@ Let code in a sandbox call an API that needs a token, while the token stays in y ## Call the API from a Container -The container sends its request to `api.github.com` as usual. The Durable Object intercepts HTTPS requests to that hostname and delivers them to an entrypoint in your Worker, which adds the token. +The container sends its request to `api.github.com` as usual. The Durable Object registers an intercept for HTTPS requests to that hostname. The intercept delivers each request to an entrypoint in your Worker, which adds the token. @@ -134,8 +134,7 @@ The container sends its request to `api.github.com` as usual. The Durable Object ], { env: { - // The intercept terminates TLS with a certificate that the container - // CA certificate signs. Node.js reads the CA certificate from this path + // Trust the CA that signs the certificate of the intercept NODE_EXTRA_CA_CERTS: "/etc/cloudflare/certs/cloudflare-containers-ca.crt", }, }, @@ -151,6 +150,8 @@ The container sends its request to `api.github.com` as usual. The Durable Object } ``` + The intercept answers each HTTPS request with its own certificate, signed by a Cloudflare certificate authority (CA). Node.js accepts the certificate only when it trusts that CA, so `NODE_EXTRA_CA_CERTS` points Node.js to the CA certificate file. The `entrypoint` option of `start()` sets the command that the container runs, and is unrelated to the entrypoint in your Worker. + 3. Add a route to your Worker that returns the username: ```ts title="src/index.ts" diff --git a/src/content/docs/sandbox/network/clone-a-private-repository.mdx b/src/content/docs/sandbox/network/clone-a-private-repository.mdx index 2018af5b33e..75339a0fbf8 100644 --- a/src/content/docs/sandbox/network/clone-a-private-repository.mdx +++ b/src/content/docs/sandbox/network/clone-a-private-repository.mdx @@ -20,7 +20,7 @@ import { WranglerConfig, } from "~/components"; -Clone a private repository into a sandbox while the access token stays in your Worker. If the clone URL contains the token, every command in the sandbox can read it. Instead, the Durable Object intercepts HTTPS requests from Git to `github.com`. An entrypoint in your Worker receives each request, checks it, and adds the token. +Clone a private repository into a sandbox while the access token stays in your Worker. If the clone URL contains the token, every command in the sandbox can read it. Instead, the Durable Object intercepts HTTPS requests from Git to `github.com` and delivers them to an entrypoint in your Worker, a class that the Worker exports to handle requests. The entrypoint checks each request and adds the token. ## Prerequisites @@ -152,8 +152,7 @@ Clone a private repository into a sandbox while the access token stays in your W ], { env: { - // The intercept terminates TLS with a certificate that the container - // CA certificate signs. Git reads the CA certificate from this path + // Trust the CA that signs the certificate of the intercept GIT_SSL_CAINFO: "/etc/cloudflare/certs/cloudflare-containers-ca.crt", }, }, @@ -169,6 +168,8 @@ Clone a private repository into a sandbox while the access token stays in your W } ``` + The intercept answers each HTTPS request with its own certificate, signed by a Cloudflare certificate authority (CA). Git accepts the certificate only when it trusts that CA, so `GIT_SSL_CAINFO` points Git to the CA certificate file. + 6. Add a route to your Worker that clones the repository: ```ts title="src/index.ts" diff --git a/src/content/docs/sandbox/network/index.mdx b/src/content/docs/sandbox/network/index.mdx index a208e75eb1f..bfa6101742b 100644 --- a/src/content/docs/sandbox/network/index.mdx +++ b/src/content/docs/sandbox/network/index.mdx @@ -17,8 +17,8 @@ import { DirectoryListing } from "~/components"; Any process in a sandbox can read a token that the sandbox holds. Keep the token in your Worker instead, and give the sandbox only the access it needs. A Container sends requests to a hostname that you choose, and your Worker intercepts each request and adds the token. A Dynamic Worker calls a method that your Worker passes it, and the method adds the token. -A Container that starts with `enableInternet: false` reaches only the hostnames you intercept. A Dynamic Worker loaded with `globalOutbound: null` cannot make its own requests, and reaches your application only through the methods you pass. +A Container that starts with `enableInternet: false` has no direct Internet access, and its HTTP and HTTPS requests reach your Worker only through the intercepts that you register. A Dynamic Worker loaded with `globalOutbound: null` cannot make its own requests, and reaches your application only through the methods you pass. -For every outbound option in each environment, refer to [Handle outbound traffic](/containers/configuration/outbound-traffic/) for Containers and [Egress control](/dynamic-workers/usage/egress-control/) for Dynamic Workers. +For the targets and DNS behavior of Container intercepts, refer to [`interceptOutboundHttp()`](/containers/api/durable-object-container/#interceptoutboundhttp) and [`interceptOutboundHttps()`](/containers/api/durable-object-container/#interceptoutboundhttps). For every outbound option of Dynamic Workers, refer to [Egress control](/dynamic-workers/usage/egress-control/). diff --git a/src/content/docs/sandbox/previews/index.mdx b/src/content/docs/sandbox/previews/index.mdx index 594dcd1b073..7c27a03716e 100644 --- a/src/content/docs/sandbox/previews/index.mdx +++ b/src/content/docs/sandbox/previews/index.mdx @@ -50,11 +50,11 @@ Use a browser to open a web server that runs in a sandbox. The server has no pub - The server must listen on `0.0.0.0`, because `getTcpPort()` cannot reach a server that listens only on `127.0.0.1`. Many development servers listen on `localhost` by default and provide an option to change the address. The server runs as the main process. When it exits, for example after a crash, the instance stops, and its files end with it. If commands in the sandbox change files that you need, start the server as described in [Run a server in the background](/sandbox/commands/run-a-server-in-the-background/) instead. + The server must listen on `0.0.0.0`, because `getTcpPort()` cannot reach a server that listens only on `127.0.0.1`. Many development servers listen on `localhost` by default and provide an option to change the address. The next step passes the command as the `entrypoint` of `start()`, so the server runs as the main process. When it exits, for example after a crash, the [container instance stops](/sandbox/concepts/lifetime/#what-stops-an-instance), and the files on its disk end with it. If commands in the sandbox change files that you need, start the server as described in [Run a server in the background](/sandbox/commands/run-a-server-in-the-background/) instead. After you deploy, servers built on the Python `http.server` module, such as `python3 -m http.server`, exit on startup, because they look up the container hostname. For a Python command that serves files, refer to [Differences after you deploy](/containers/guides/local-dev/#the-hostname-is-longer-than-a-dns-label). -2. Add an inactivity timeout to your Durable Object. The constructor sets the timeout again when a restarted Durable Object finds the container running: +2. Add an inactivity timeout to your Durable Object. A Durable Object restarts when it leaves memory and later runs its constructor again, for example after a deploy, and the timeout does not survive the restart. The constructor sets the timeout again when it finds the container running: ```ts title="src/index.ts" const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; @@ -215,7 +215,7 @@ Use a browser to open a web server that runs in a sandbox. The server has no pub ## Keep WebSocket connections open -The `fetch()` handler in the previous section passes WebSocket upgrade requests to the server, so WebSocket connections work. A connection that only passes through the Durable Object does not keep the sandbox running. When the inactivity timeout ends, Cloudflare stops the instance and the connection closes. +The `fetch()` handler in the previous section passes WebSocket upgrade requests to the server, so WebSocket connections work. A connection that only passes through the Durable Object does not [keep the sandbox running](/sandbox/concepts/lifetime/#activity-keeps-an-instance-running), so the connection closes when the inactivity timeout stops the instance. To keep the sandbox running while a page has a connection open, accept both ends of the connection in the Durable Object: @@ -283,7 +283,7 @@ To keep the sandbox running while a page has a connection open, accept both ends -With the bridge, the sandbox keeps running while any page has a connection open, even when no messages arrive. After the last connection closes, the inactivity timeout applies again. +With the bridge, the sandbox keeps running while any page has a connection open, even when no messages arrive. After the last connection closes, the [inactivity timeout](/sandbox/concepts/lifetime/#activity-keeps-an-instance-running) applies again. ## Use hot reloading with a development server diff --git a/src/content/docs/sandbox/previews/serve-previews-on-their-own-hostnames.mdx b/src/content/docs/sandbox/previews/serve-previews-on-their-own-hostnames.mdx index 810997ac0b1..63c2d6d99fa 100644 --- a/src/content/docs/sandbox/previews/serve-previews-on-their-own-hostnames.mdx +++ b/src/content/docs/sandbox/previews/serve-previews-on-their-own-hostnames.mdx @@ -21,7 +21,7 @@ import { } from "~/components"; import { PreviewHostname } from "~/components/react/sandbox/PreviewHostname"; -Serve each sandbox preview from its own hostname, such as `ada.example-previews.com`. Code in a preview runs in the visitor's browser, and each preview hostname is a separate origin. Code in one preview cannot read your application pages or the storage of another preview. Paths reach the server unchanged, so applications do not need a base path. +Serve each sandbox preview from its own hostname, such as `ada.example-previews.com`. Code in a preview runs in the visitor's browser, and each preview hostname is a separate origin. An origin is the scheme, hostname, and port of a URL, and browsers separate pages and storage by origin. Code in one preview cannot read your application pages or the storage of another preview. @@ -102,7 +102,7 @@ In `https://ada.example-previews.com/app/`, a wildcard DNS record and the route - The server receives the full path and the preview hostname in the `Host` header. + The server receives the full path and the preview hostname in the `Host` header, so applications do not need a base path. Every request to a preview hostname goes to the preview. Keep routes that manage previews, such as stopping one, on other hostnames, so code in a preview cannot call them from its own origin. Authenticate preview visitors on the preview hostname, and do not send your application session cookie there. diff --git a/src/content/docs/sandbox/reference/directory-backups.mdx b/src/content/docs/sandbox/reference/directory-backups.mdx index cdd21ab8760..81e4e51ba20 100644 --- a/src/content/docs/sandbox/reference/directory-backups.mdx +++ b/src/content/docs/sandbox/reference/directory-backups.mdx @@ -15,7 +15,7 @@ reviewed: 2026-09-25 import { MetaInfo, Type, TypeScriptExample } from "~/components"; -`DirectoryBackup` saves a directory from a running container to an R2 bucket and restores it later into a container, including a container that runs a newer image. The container sends the backup through `DirectoryBackupGateway` in your Worker, so the container needs no Internet access and no R2 credentials. The gateway lets the container reach only the object that the operation needs. `DirectoryBackup` does not start or stop the container. +`DirectoryBackup` saves a directory from a running container to an R2 bucket and restores it later into a container, including a container that runs a newer image. The container sends the backup through `DirectoryBackupGateway` in your Worker, so the container needs no Internet access and no R2 credentials. The gateway lets the container reach only the object that the operation needs. @@ -50,7 +50,7 @@ export class MyContainer extends DurableObject { dir: "/workspace/app", exclude: ["node_modules/"], }); - // Keep the record. The package does not list backups. + // Keep the record. await this.ctx.storage.put("workspace", backup); } @@ -120,10 +120,10 @@ restore(backup: DirectoryBackupRecord, options?: DirectoryRestoreOptions): Promi Replaces the target directory with the contents of the backup. The target is `dir` from the record, unless you pass another absolute path in `options.dir`. - The parent directory of the target must exist. The target does not need to exist. -- The target must not be a mount point or contain one, such as an [`S3Mount`](/sandbox/reference/s3-mounts/) mount. Otherwise `restore()` fails with `EBUSY`, including when a mount appears during the restore. Unmount first, for example with `S3Mount.unmount()`, and mount again after the restore. +- The target must not be a mount point or contain one, such as an [`S3Mount`](/sandbox/reference/s3-mounts/) mount. Otherwise `restore()` fails with `EBUSY`, including when a mount appears during the restore. - `restore()` extracts the backup into a new directory beside the target and checks the size and SHA-256 of the object against the record. It then replaces the target with the new directory in one atomic rename. When `restore()` throws `SandboxFileError` or `SandboxBackupError`, or is canceled before it verifies the download, it leaves the target as it was and removes the new directory. -- A process that has its working directory inside the target, or has a file in the target open, keeps using the old directory or file. Restart such processes after `restore()`, or have them reopen files by absolute path. -- The filesystem needs free space for the old and the restored directory at the same time. `restore()` stops with `ENOSPC` before free space falls below 5% of the filesystem or 256 MiB, whichever is smaller. +- A process that has its working directory inside the target, or has a file in the target open, keeps using the old directory or file. Such a process reaches the restored files only after it restarts or reopens them by absolute path. +- The filesystem needs free space for the old and the restored directory at the same time. `restore()` stops with `ENOSPC` before free space becomes less than 5% of the filesystem or 256 MiB, whichever is smaller. - The root filesystem of a deployed container compresses files. `restore()` writes files without compression. The restored files take more disk space until a process rewrites them. ### `delete` @@ -144,11 +144,11 @@ Routes backup traffic from the container to `DirectoryBackupGateway`, which refu {/* Remove the second sentence and the last list item when a hostname intercept registered after interceptAllOutboundHttp() receives its requests. */} -`backup()` and `restore()` add this route themselves. If the Durable Object also calls [`interceptAllOutboundHttp()`](/containers/api/durable-object-container/#interceptalloutboundhttp), call `intercept()` before it. Otherwise the catch-all receives the backup traffic from the container, and `backup()` and `restore()` fail with `BACKUP_TRANSFER`. +`backup()` and `restore()` add this route themselves. If the Durable Object also calls [`interceptAllOutboundHttp()`](/containers/api/durable-object-container/#interceptalloutboundhttp), `intercept()` must run before it. Otherwise the catch-all receives the backup traffic from the container, and `backup()` and `restore()` fail with `BACKUP_TRANSFER`. - Call `intercept()` in the setup that runs after you start the container, and again when a restarted Durable Object finds the container running. Backups and restores in that container then keep their route. - Do not call `intercept()` while a backup or restore can be running. It replaces the access of the running operation, and the operation fails. -- A container that registered the catch-all first keeps sending backup traffic to it. Restart the container to use `intercept()`. +- A container that registered the catch-all first keeps sending backup traffic to it until the container restarts. ```ts #setup: Promise | null = null; @@ -167,6 +167,8 @@ Routes backup traffic from the container to `DirectoryBackupGateway`, which refu } async #setUp(container: Container): Promise { + // `sandbox` is an image in the Wrangler configuration, and `Outbound` + // is a WorkerEntrypoint that the main module of the Worker exports. if (!container.running) { container.start({ image: container.images.sandbox, @@ -234,9 +236,9 @@ A backup keeps: A backup does not keep: -- Sockets, devices, and FIFOs +- Sockets, device files, and named pipes (FIFOs) - The setuid and setgid bits -- Extended attributes, ACLs, and file capabilities +- [Extended attributes](https://man7.org/linux/man-pages/man7/xattr.7.html), access control lists (ACLs), and [file capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) - Holes in sparse files, which restore as zeros - Access times diff --git a/src/content/docs/sandbox/reference/files.mdx b/src/content/docs/sandbox/reference/files.mdx index e741bfb2f7d..1ff8d892777 100644 --- a/src/content/docs/sandbox/reference/files.mdx +++ b/src/content/docs/sandbox/reference/files.mdx @@ -14,7 +14,7 @@ reviewed: 2026-09-23 import { TypeScriptExample } from "~/components"; -`Files` reads and changes files and directories in a running container. It does not start or stop the container. +`Files` reads and changes files and directories in a running container. Each method runs the `sandbox-shim` helper binary in the container through [`exec()`](/containers/api/durable-object-container/#exec). Every method except `readFile()` waits for the helper to finish; `readFile()` returns once the file is open and streams its contents. Paths follow Linux rules, and `Files` does not restrict which paths a caller can open. @@ -67,7 +67,7 @@ new Files(container: Pick) - A relative path requires `cwd`. `cwd` must be absolute. - A path cannot be empty or contain a null character. - An invalid path or `cwd` throws `TypeError` before the helper process starts. -- A relative path is joined onto `cwd`, and Linux resolves the joined path. An absolute path ignores `cwd`. A `cwd` that does not exist rejects with `SandboxFileError` `ENOENT`. So does a missing directory anywhere in a path. +- A relative path is joined onto `cwd`, and Linux resolves the joined path. An absolute path ignores `cwd`. For a relative path, a `cwd` that does not exist rejects with `SandboxFileError` `ENOENT`. So does a missing directory anywhere in a path. - Paths resolve through symbolic links as they do in Linux, except where a method states otherwise. ## Options @@ -103,8 +103,9 @@ Reads the contents of a file as bytes. - Returns a `Response` with status `200` and no `Content-Type` header. The body applies backpressure to the helper process. - Rejects with `SandboxFileError` when the file cannot be opened, such as `ENOENT` for a missing file or `EISDIR` for a directory. - An error after the file opens errors the response body instead of rejecting the call. +- The operation finishes when the response body is read to the end or canceled. Aborting `signal` before then errors the body with the abort reason and kills the helper process. -Read the `Response` like any HTTP response. `text()`, `json()`, `arrayBuffer()`, and `blob()` load the whole file into memory. For a large file or a file of unknown size, stream `body` instead. +Read the `Response` like any HTTP response. `text()`, `json()`, `arrayBuffer()`, and `blob()` load the whole file into memory. Reading `body` as a stream does not. ### `writeFile` @@ -128,7 +129,7 @@ Creates a file, or truncates an existing file, then streams `content` into it. - The file opens before a `ReadableStream` is consumed. A later failure can leave the file created, truncated, or partially written. - If the `content` stream errors, the call rejects with the error from the stream instead of a `SandboxFileError`. -`rename()` replaces an existing destination in one step. To keep other processes from reading a partially written file, write the file to a temporary path and then rename it: +`rename()` replaces an existing destination in one step. Writing a file to a temporary path and then renaming it keeps other processes from reading a partially written file: ```ts await files.writeFile(`${path}.partial`, content, { cwd }); @@ -168,7 +169,7 @@ Returns the immediate entries of a directory as [`SandboxDirectoryEntry`](#sandb - A file at `path` rejects with `ENOTDIR`. - An entry name that is not valid UTF-8 rejects the whole call with `EILSEQ`. -To list a directory tree, walk it with `readDirectory()`. This walk does not follow symbolic links to directories: +A recursive walk with `readDirectory()` lists a directory tree. This walk does not follow symbolic links to directories: ```ts import type { Files } from "@cloudflare/sandbox"; @@ -185,7 +186,7 @@ async function* walk(files: Files, directory: string): AsyncGenerator { } ``` -Each call to `readDirectory()` starts one process in the container. For a large tree, one `container.exec(["find", directory])` call is faster. To cancel it, pass `signal` to `exec()`. +Each call to `readDirectory()` starts one process in the container. For a large tree, one `container.exec(["find", directory])` call is faster. The `signal` option of `exec()` cancels it. ### `mkdir` @@ -264,7 +265,7 @@ A string is written as UTF-8. | `modifiedAt` | `Date` | Last content modification time | | `changedAt` | `Date` | Last metadata change time | -`JSON.stringify()` and `Response.json()` throw `TypeError` for a `bigint`. Convert `size` with `Number()` or `String()` before serializing a `SandboxFileStat`. `bigint` and `Date` values pass through Durable Object RPC unchanged. +`JSON.stringify()` and `Response.json()` throw `TypeError` for a `bigint`, so convert `size` with `Number()` or `String()` before serializing a `SandboxFileStat`. `bigint` and `Date` values pass through Durable Object RPC unchanged. ### `SandboxDirectoryEntry` diff --git a/src/content/docs/sandbox/reference/index.mdx b/src/content/docs/sandbox/reference/index.mdx index 12be636c791..c3fb4897f67 100644 --- a/src/content/docs/sandbox/reference/index.mdx +++ b/src/content/docs/sandbox/reference/index.mdx @@ -21,7 +21,7 @@ import { WranglerConfig, } from "~/components"; -`@cloudflare/sandbox` works with a container you start from a Durable Object. Its classes run helper processes in the running container through [`exec()`](/containers/api/durable-object-container/#exec). The package does not start, stop, or monitor the container. +`@cloudflare/sandbox` works with a container that a [Durable Object](/durable-objects/) starts. A Durable Object is a named object that handles requests and keeps its own storage. The classes of the package run helper processes in the running container through [`exec()`](/containers/api/durable-object-container/#exec). The package does not start, stop, or monitor the container. For `start()`, `exec()`, snapshots, and the other methods of the container itself, refer to the [Durable Object container API](/containers/api/durable-object-container/). @@ -33,7 +33,7 @@ For `start()`, `exec()`, snapshots, and the other methods of the container itsel Every class in the package has these requirements. A class page lists any others. -- The container image contains the helper binary at `/usr/local/bin/sandbox-shim`. Copy it from the `cloudflare/sandbox` image whose tag matches the installed `@cloudflare/sandbox` version: +- The [container image](/containers/api/durable-object-container/#images), which supplies the filesystem of the container, contains the helper binary at `/usr/local/bin/sandbox-shim`. Copy it from the `cloudflare/sandbox` image whose tag matches the installed `@cloudflare/sandbox` version: ```dockerfile COPY --from=docker.io/cloudflare/sandbox: /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim diff --git a/src/content/docs/sandbox/reference/s3-mounts.mdx b/src/content/docs/sandbox/reference/s3-mounts.mdx index 2fc297cecc6..439043ae83b 100644 --- a/src/content/docs/sandbox/reference/s3-mounts.mdx +++ b/src/content/docs/sandbox/reference/s3-mounts.mdx @@ -15,7 +15,7 @@ reviewed: 2026-09-23 import { MetaInfo, Type, TypeScriptExample } from "~/components"; -`S3Mount` mounts an S3-compatible bucket, or a prefix in one, at a path in a running container. The container runs `s3fs`, which sends storage requests to `S3Gateway` in your Worker. `S3Gateway` checks each request and signs it with credentials that the container never receives. `S3Mount` does not start or stop the container. +`S3Mount` mounts an S3-compatible bucket, or a prefix in one, at a path in a running container. The container runs [`s3fs`](https://github.com/s3fs-fuse/s3fs-fuse), which presents the bucket as files through FUSE (Filesystem in Userspace), a Linux interface for filesystems that run as ordinary processes. `s3fs` sends storage requests to `S3Gateway` in your Worker. `S3Gateway` checks each request and signs it with credentials that the container never receives. @@ -67,7 +67,7 @@ For a complete Worker, refer to [Mount an R2 bucket](/sandbox/files/mount-an-r2- In addition to the [package requirements](/sandbox/reference/#requirements), `S3Mount` has these requirements: -- The container image contains FUSE 3 and `s3fs`. In Debian and Ubuntu images, install the `fuse3` and `s3fs` packages. +- The container image contains FUSE 3 and `s3fs`. Debian and Ubuntu provide them in the `fuse3` and `s3fs` packages. - The main module of the Worker re-exports `S3Gateway`, so that `this.ctx.exports.S3Gateway` exists: ```ts @@ -98,15 +98,15 @@ Mounts the source at `request.mountPath`. The result depends on what the path ho - Nothing: `mount()` creates the directory and mounts the source. - A mount with the same settings: `mount()` reuses it. - Leftover state from a failed or canceled `mount()` with the same settings: `mount()` repairs it. -- Another filesystem, or a mount with different settings: `mount()` throws `SandboxS3MountError` with code `S3_MOUNT_CONFLICT`. To change the settings, call `unmount()` first. +- Another filesystem, or a mount with different settings: `mount()` throws `SandboxS3MountError` with code `S3_MOUNT_CONFLICT`. A mount takes new settings only after `unmount()`. Each new mount adds one outbound intercept target to the container. Reusing a mount does not add one. `unmount()` does not remove one. Mounts share the container [intercept limit](/containers/api/durable-object-container/#intercept-limit) with the intercepts your own code registers. Once the container reaches the limit, a mount with new settings fails. {/* Remove this paragraph when a hostname intercept registered after interceptAllOutboundHttp() receives its requests. */} -If the Durable Object also calls [`interceptAllOutboundHttp()`](/containers/api/durable-object-container/#interceptalloutboundhttp), call `mount()` before it, right after you start the container. Otherwise the catch-all receives the storage requests from the container, and `mount()` fails with `cannot verify the new FUSE connection`. A container that registered the catch-all first keeps sending storage requests to it. Restart the container to mount. +If the Durable Object also calls [`interceptAllOutboundHttp()`](/containers/api/durable-object-container/#interceptalloutboundhttp), `mount()` must run before it. Otherwise the catch-all receives the storage requests from the container, and `mount()` fails with `cannot verify the new FUSE connection`. A container that registered the catch-all first keeps sending storage requests to it until the container restarts. -`S3Mount` does not retry a failed operation. Call `inspect()` before you retry. +`S3Mount` does not retry a failed operation. `inspect()` reports the state that a failed operation left. ### `inspect` @@ -116,7 +116,7 @@ inspect(mountPath: string, options?: S3MountOperationOptions): Promise Denies further storage requests for the mount, then unmounts the path. On a path with no mount, `unmount()` succeeds without changes. `unmount()` never forces an unmount. -- When a process is using the path, `unmount()` throws `S3_MOUNT_BUSY`. Requests stay denied. Stop the process and call `unmount()` again, or call `mount()` with the same request to restore access. +- When a process is using the path, `unmount()` throws `S3_MOUNT_BUSY`. Requests stay denied until a later `unmount()` succeeds, or until `mount()` with the same request restores access. - After denial, new storage requests for the mount receive HTTP `403`. A request already in progress can complete. - After denial, a process that still uses the path can read empty files and lose writes without an error. Listing a directory fails. - A file that a process opened and read before the denial can stay readable through that open file. -- Canceling `unmount()` or `mount()` can leave the filesystem mounted with requests denied. Call `mount()` with the same request, or call `unmount()` again. +- Canceling `unmount()` or `mount()` can leave the filesystem mounted with requests denied. A later `mount()` with the same request restores access, and a later `unmount()` completes the unmount. ## Types @@ -189,7 +189,7 @@ Provider credentials: ### `S3MountInspection` -Every inspection has `mountPath` and `attachment.status`: +Every inspection has `mountPath` and `attachment.status`. The status decides which other fields are present: | `attachment.status` | Other fields | Meaning | | ------------------- | --------------------------------------------- | ------------------------------------------------- | @@ -199,10 +199,23 @@ Every inspection has `mountPath` and `attachment.status`: | `stale` | `attachment.configuration`, `gateway` | Mount settings remain, but the filesystem is gone | | `managed` | `attachment.configuration`, `fuse`, `gateway` | The mount is active | -- `configuration` — the endpoint, region, bucket, key prefix, access mode, and `s3fs` options of the mount. -- `fuse.status` -- `gateway.status` -- `gateway.upstream.status` — present when `gateway.status` is `reachable`. +- `attachment.configuration` — the settings of the mount, without credentials: + - `source` — `type`, `endpoint`, `region`, and `bucket` of the source. + - `keyPrefix` — the key prefix, ending in `/`. Omitted when the mount shows the whole bucket. + - `access` — the access mode. + - `s3fsOptions` — the `s3fs` options of the mount. A flag has no `value`. +- `fuse.status` — the state of the FUSE connection at the path: + - `connected` — the filesystem responds. + - `disconnected` — the FUSE connection is gone, for example because `s3fs` exited. `mount()` with the same request repairs it. + - `indeterminate` — the helper cannot tell. `fuse.detail` contains the error. +- `gateway.status` — the result of the list request that the container sends through `S3Gateway`: + - `unreachable` — the request did not reach `S3Gateway` or got no response. `gateway.detail` describes the failure. + - `error` — `S3Gateway` returned an error of its own or an invalid response. `gateway.reason` is `"credential-provider"`, `"protocol"`, or `"internal"`, and `gateway.detail` describes the failure. + - `reachable` — `S3Gateway` handled the request. `gateway.upstream` contains the result. +- `gateway.upstream.status` — the outcome of the list request: + - `usable` — the list request succeeded. + - `unavailable` — the request failed, or the S3 API returned HTTP `429` or a `5xx` status. `gateway.upstream.detail` describes the failure. + - `rejected` — the S3 API refused the request, or `S3Gateway` denied it. `gateway.upstream.reason` is `"credentials"`, `"access"`, `"not-found"`, or `"other"`, and `gateway.upstream.detail` describes the refusal. ## `S3Gateway` @@ -224,7 +237,7 @@ A mounted directory maps files to objects in the bucket. It does not behave like - `s3fs` caches file metadata, including the fact that a file does not exist, for 900 seconds by default. - Until the cache expires, a file that another client adds to the bucket can stay missing in the container. - Until the cache expires, a file that another client changes can keep its old size, and a read can return the new content cut to that size. -- To shorten the wait, set a lower `stat_cache_expire` in `s3fsOptions`. To stop caching missing files, set `disable_noobj_cache: true`. +- The `stat_cache_expire` option in `s3fsOptions` sets the cache duration in seconds. `disable_noobj_cache: true` stops caching missing files. - An interrupted write can leave an incomplete upload in the bucket. - A [snapshot](/containers/api/durable-object-container/#snapshotcontainer) does not include mounted directories.