# Runtimes

A runtime is where a run executes. A Workbench declares the runtimes it supports in its manifest; the first is the default. Choose another with `--runtime` on `run`, `smoke`, `build`, and `create`. A resumed session keeps the runtime it started on.

```sh
wb view project-core                        # lists every declared runtime
wb smoke project-core --runtime docker
wb run project-core --runtime e2b --task "Review this migration"
```

| Runtime   | Needs                                                                                 | Edits                          |
| --------- | ------------------------------------------------------------------------------------- | ------------------------------ |
| `local`   | The runner and tools on the host.                                                     | Land in your working tree.     |
| `docker`  | A Docker-compatible CLI and daemon. The image holds the runner and tools.             | Land in the mounted workspace. |
| `e2b`     | An E2B API key. The image holds the runner, tools, `git`, and GNU `tar`.              | Return as a pending outcome.   |
| `daytona` | A Daytona API key and a published image with the runner, tools, `git`, and GNU `tar`. | Return as a pending outcome.   |

## Requirements

Before anything is prepared, the Workbench's `requirements` are checked against the selected runtime. A requirement a runtime cannot verify is reported as unchecked, never as satisfied.

| Runtime   | How requirements are checked                                                                                                                                                                                                                          |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `local`   | Compares `os`, `arch`, CPU count, and total memory with the host. `disk_gb` is not checked. A GPU requirement passes when `nvidia-smi --query-gpu=name,memory.total` (Linux) or `system_profiler SPDisplaysDataType` (macOS) lists a GPU.             |
| `docker`  | Needs `linux` in `os` or no `os`, and the host architecture in `arch`. Applies `cpu` and `memory_gb` as container limits. Runs a GPU requirement only when `docker info` lists the NVIDIA container runtime, and passes `--gpus all`. Never on macOS. |
| `e2b`     | Needs `linux` in `os` or no `os`. Refuses `gpu: true`. Does not enforce `arch`, `cpu`, `memory_gb`, or `disk_gb`; `smoke` reports them as unchecked.                                                                                                  |
| `daytona` | The class must match `os`. Runs only the `linux` class and refuses `gpu: true`. Applies `cpu`, `memory_gb`, and `disk_gb` as the sandbox's resources, defaulting to 2 vCPUs and 4 GiB when not declared. Reports `arch` as unchecked.                 |

When local GPU discovery finds nothing, the run is refused with the reason, such as `nvidia-smi is not installed`. Pass `--allow-unchecked-gpu` to `run`, `smoke`, or `create` to run anyway on a GPU discovery cannot see.

## local

The run executes directly on the host, in the selected directory. Declared tools must be on the host's `PATH`. Declared workspace access is checked before launch, but local execution is not an operating-system sandbox: the run can reach anything your account can, including credentials already on the machine.

The runner uses its normal local sign-in. See [Models and connections](/docs/cli/models).

## docker

The run executes in the Workbench's declared image: a published OCI image, or a package-local Dockerfile.

```yaml
runtimes:
    docker:
        image:
            build: ./Dockerfile.workbench
            context: .
```

`run` pulls or builds the image automatically; `wb build` does it ahead of time. Published tags run by their resolved digest.

- The workspace is mounted read-write at `/workspace` and the package read-only. Named workspaces mount at `/workspaces/<name>` with their declared access.
- A Workbench that declares a variable the Docker CLI reads itself (`DOCKER_*`, `BUILDKIT_*`, `BUILDX_*`, `COMPOSE_*`, `XDG_*`, `HOME`, `PATH`, `TMPDIR`) is refused: `A Docker run cannot pass NAME to the container: the Docker CLI reads that name itself`.
- The container's root filesystem is read-only and `/tmp` is writable. Images must tolerate that and write caches beneath the provided temporary `HOME`.
- Where the host exposes a numeric user and group, the container runs as that identity so files keep host ownership.
- Runner credentials that Workbench manages live in a private per-runner named volume.
- Multi-turn sessions keep the runner in the container. A runner's HTTP service is published only on a dynamically assigned host loopback port.

Docker Desktop mediates bind mounts through its virtual machine, so performance and permissions can differ from native Linux. A git worktree does not work as a Docker workspace, because its `.git` file points at a parent repository that is not mounted: use a full clone. Containers and the host also conflict over `node_modules` built for different platforms, so give a containerized run its own clone.

### Host Docker access

A Workbench that must build or run sibling containers declares it:

```yaml
runtimes:
    docker:
        image: ghcr.io/example/project-workbench:0.4.0
        docker:
            engine:
                mode: host
```

The declaration is not authorization. Every `smoke`, `run`, `resume`, and `create` needs an explicit grant:

```sh
wb run project-core --allow-host-docker --task "Start the local stack"
```

Without it: `Host Docker engine access requires explicit --allow-host-docker authorization`. Host Docker access is effectively administrative access to the Docker host and can escape the container's isolation. The image must contain the Docker CLI. Host-engine runs keep host workspace paths inside the container so nested bind mounts resolve.

## e2b

The run executes in a fresh E2B sandbox created from a reusable template built from the Workbench's image.

```sh
wb connect --runtime e2b
wb build project-core
wb smoke project-core
wb run project-core --runtime e2b --task "Review this migration"
```

The key comes from `E2B_API_KEY`, or from `wb connect --runtime e2b`, which saves it on the host. An inherited `E2B_API_KEY` overrides the saved key. The key stays on the host and never enters the sandbox.

- The image can be a public OCI reference or a package-local Dockerfile. Use versioned tags or digests, because an existing template is reused.
- The image must include the runner, every declared tool, `git`, and GNU `tar` with `--null` support.
- The workspace is staged at `/workspace`, named workspaces at `/workspaces/<name>`, and the package at `/workbench`.
- Gitignored files, repository metadata, dependency trees, common credential stores, and secret-bearing files such as `.env` are not uploaded.
- Changes return as pending changesets. Apply them with `wb outcome <id> --apply`.
- Input and output transfers each have a 512 MiB limit.
- A resumed session runs in a fresh sandbox after its native session state and credentials are copied in.
- Sandboxes have a 60 minute provider timeout, which pauses a crash survivor.
- Terminal events include E2B duration, resources, and a marked infrastructure cost estimate.

`wb smoke` on E2B creates a real, billable sandbox.

## daytona

The run executes in a fresh Daytona sandbox created from the published image on the Workbench's Daytona entry.

```yaml
runtimes:
    daytona:
        class: linux
        image: ghcr.io/example/project-workbench:0.4.0
```

```sh
wb connect --runtime daytona
wb smoke project-core --runtime daytona
wb run project-core --runtime daytona --task "Review this migration"
```

The key comes from `DAYTONA_API_KEY`, or from `wb connect --runtime daytona`. An inherited `DAYTONA_API_KEY` overrides the saved key. `DAYTONA_API_URL` selects a Daytona API endpoint other than the public one. The key is never printed and never passed to the sandbox.

- Only the `linux` class runs. `windows`, `macos`, and `gpu` fail with `class X is not available yet`.
- The image must be published. A package-local Dockerfile is refused; there is no fallback to another runtime's image. Without one: `The daytona runtime needs an image. Set runtimes.daytona.image.`
- The image must include the runner, every declared tool, `git`, and GNU `tar` with `--null` support.
- OpenCode, Pi, and Claude Code all run. OpenCode uses its HTTP service; Pi and Claude Code use piped standard input and streamed toolbox logs. Claude Code requires version 2.1.318 or newer in the image.
- Model credentials come from the environment or `--env-file`. There is no credential store and no in-run sign-in.
- Staging, exclusions, the 512 MiB transfer limits, and pending outcomes match E2B.
- The sandbox has a 60 minute lifetime. The engine sets Daytona's wall-clock TTL, so it is destroyed even if the CLI dies. It carries the labels `dev.workbenches.managed`, `dev.workbenches.run`, and `dev.workbenches.scope`.
- No outcome recovery and no cost estimate.

`wb smoke` on Daytona creates a real, billable sandbox.

## Save a runtime key

```sh
wb connect --runtime e2b              # masked prompt
wb connect --runtime e2b --stdin      # for automation
wb connect --runtime e2b --status
wb connect --runtime e2b --remove
```

Daytona takes the same commands with `--runtime daytona`. There is no flag that takes the key as a value, so it never enters shell history. Both keys are stored in `~/.workbench/runtime.secrets.json` with mode `0600`, separately from model credentials. Saving a key creates no sandbox and costs nothing.
