> ## Documentation Index
> Fetch the complete documentation index at: https://iii.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading to the sandbox worker

> Move sandboxes and VM workers from the engine to the sandbox-libkrun worker, and clean up what 0.25 left behind.

iii 0.26 moves the VM code out of the engine. `iii-worker` and `iii-init` are gone, and so is the
`iii-sandbox` worker you configured under `engine.workers`. The engine keeps `iii-sandbox` as a small
router: it serves the `sandbox::*` functions and forwards every call to a sandbox adapter. The
`sandbox-libkrun` worker is that adapter. It runs the VMs, it comes from the worker registry, and
Compose installs it when a project needs it.

Consumers keep calling `sandbox::*` in the `default` namespace, and no existing request field,
response field, or S-code changed. The `sandbox::exec` and `sandbox::run` replies gain two keys,
`stdout_truncated` and `stderr_truncated` (contract 1.1, see
[Sandboxes](https://iii.dev/docs/next/creating-workers/sandboxes.md#lifecycle)). What changes is where you configure sandboxes
and where their state is kept. Work through the sections that apply to your setup. For the rest of
0.26, see [Upgrading from 0.25.x to 0.26.x](https://iii.dev/docs/next/upgrading/from-0-25-x.md).

## What moved

| 0.25 | 0.26 |
| - | - |
| `engine.workers.iii-sandbox` | `engine.sandbox`, and `containers.sandbox-libkrun.config_override` for the settings |
| `iii-worker` boots bundle and `runtime.base_image` VMs | `sandbox-libkrun` boots them through Compose |
| `iii-worker` and `iii-init` in `~/.local/bin` | Not installed or updated by `iii update` |
| VM state in `<state dir>/vm/<key>/` | VM state in `~/.iii/sandbox/workers/<vm_id>/` |
| Error `docs_url` on GitHub | [Sandboxes](https://iii.dev/docs/next/creating-workers/sandboxes.md), where every S-code has an entry |

## Replace `engine.workers.iii-sandbox`

A compose file that still declares `iii-sandbox` under `engine.workers` fails `iii compose --up`
with `SANDBOX_CONFIG_MOVED`. The error prints the replacement with your old block filled in:

```yaml worker-compose.yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
containers:
  sandbox-libkrun:
    worker: package://sandbox-libkrun
    version: '^0.3'
    config_override:
      image_allowlist:
      - python
      - node
```

Delete the `iii-sandbox` entry. If it was the only entry, write `workers: {}`: an empty `workers:`
key fails with `INVALID_COMPOSE_FILE`, and an `engine:` key with nothing under it means Compose starts
no engine and attaches to whichever one holds its address. Add that container and keep the keys you had
(`image_allowlist`, `custom_images`, the `default_*` limits, and the rest): they are the same under
`config_override`. You declared the container, so Compose never removes it.

A file that already declares the adapter at `version: '^0.2'` stays on 0.2 across `compose::up`,
`compose::restart`, and `compose::update worker=sandbox-libkrun`. To move it, stop Compose, change
the range to `'^0.3'`, and run `iii compose --up` again, or run a bare `iii call compose::update`,
which rewrites the range to `^0.3` and [updates every other package
worker](https://iii.dev/docs/next/using-iii/compose.md#updating-workers). On 0.2, observers can read the project's token.

Before 0.3.1, a VM worker whose directory contains the compose file, such as `path://.`, can read the
project's token, and its disk keeps those bytes after the upgrade. If you ran one, stop Compose and
run `iii compose --up` once you are on 0.3.1, which replaces the token. Updating or restarting the
adapter keeps it.

`engine.sandbox.adapter` names the adapter's function prefix and defaults to `sandbox-libkrun`, so you
can leave the section out. `engine.sandbox.namespace` names the namespace the adapter runs in and
defaults to the project's namespace. See [Compose](https://iii.dev/docs/next/using-iii/compose.md#engine-fields).

## Configuration stored under `iii-sandbox`

0.25 read its sandbox settings only from the engine config, and the previous section moves those to
`config_override`. There is normally nothing stored to move: 0.25 never registered the id
`iii-sandbox`, so a plain `configuration::set` failed with `NOT_REGISTERED`, and the console's
Configuration page did not list it. To check, read it in its template form:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii call configuration::get --json '{"id": "iii-sandbox", "raw": true}'
```

A `NOT_FOUND` error means there is nothing to do. An entry exists only if someone added it by hand,
for example with `configuration::register` and then `configuration::set`. Compose does not move it,
and it never applied on 0.25, because the sandbox daemon read only its own config file. Review it
before you copy any key into `config_override`: there, values such as `image_allowlist` take effect
for the first time.

Do not store those keys under the adapter's id, `<namespace>-sandbox-libkrun`: Compose sets that
entry in memory each time it starts the adapter, and storing it with `configuration::set` fails with
`NOT_REGISTERED`. A running project reads the edited file on `compose::restart` without a `worker`
argument. Check the adapter before you delete the old entry.

## Change adapter settings

Compose builds the adapter's entry from three layers, lowest to highest: the package defaults, the
entry's current value in the engine, then `config_override`. Maps such as `custom_images` merge key
by key; lists such as `image_allowlist` and scalars replace. See [Configuration
precedence](https://iii.dev/docs/next/using-iii/compose.md#configuration-precedence).

Removing a key from `config_override` therefore does not restore its default: the key keeps its
current value, and a `custom_images` entry you delete stays. To go back to the package defaults,
start from an engine that has no entry. If `iii compose --up` started the engine, stop Compose and
run `iii compose --up` again. If Compose attaches to an engine you run yourself, restart that
engine, then run `compose::restart`.

A bad value fails closed: the adapter exits at start, and `sandbox::*` calls fail with S301.
Removing the key does not recover it, because the bad value stays in the entry. Set the key to a
valid value in `config_override` and run `compose::restart` without a `worker` argument, or start
from an engine that has no entry, as above. A `custom_images` entry whose name is bad, such as one
that names a built-in preset, has no valid value, and renaming it leaves the old entry in place:
start from an engine that has no entry.

## Engines that no Compose file manages

Compose binds the router to `sandbox-libkrun` only on an engine it owns. An engine started directly
with `iii --config config.yaml`, with no `engine:` section in any Compose file, has no adapter bound,
and its `sandbox::*` calls fail with S303. Set the top-level `sandbox:` section in that engine's
config:

```yaml config.yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
sandbox:
  adapter: sandbox-libkrun
  namespace: default
```

`namespace` is where the adapter runs, `default` when omitted. Until the adapter runs there, calls
fail with S301, which names the namespace and how to start or install it.

If a Compose project already runs in that namespace, add the adapter to it with
`iii call compose::add worker=iii-sandbox`. If none runs, `compose::add` has nothing to edit: it
only edits a compose file that exists, and a file with no `engine:` section must declare at least
one container. Write a `worker-compose.yaml` that declares the adapter:

```yaml worker-compose.yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
containers:
  sandbox-libkrun:
    worker: package://sandbox-libkrun
    version: '^0.3'
```

It has no `engine:` section, so Compose attaches to your engine instead of starting one. Start it
in the bound namespace, with your engine's address:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii compose --up --namespace default --engine ws://127.0.0.1:49134
```

Leaving an `iii-sandbox` entry in the list-shaped `config.yaml` fails startup with
`UNSUPPORTED_CONFIG_WORKERS`.

## Frozen installs

`iii compose --up --frozen` and `iii compose build --frozen` never edit the compose file. A project
that needs the adapter (it sets `engine.sandbox` for its own namespace or has a VM worker) but does
not declare it fails with `SANDBOX_ADAPTER_NOT_DECLARED`. Run `iii compose --up` once without
`--frozen`, so Compose writes the container and the lock, then commit both.

## VM workers from `path://`

The adapter only mounts host directories under its host roots: the compose file's directory,
Compose's package cache (`~/.iii/compose/packages`, or `$III_COMPOSE_STATE_DIR/packages`), and the
directory of each `path://` worker with `runtime.base_image` that the file declares when the adapter
starts. A declared worker outside the compose directory, such as `path://../shared`, is admitted.

The adapter reads its roots only when it starts. A `path://` VM worker outside the compose
directory that you add while it runs, for example with `compose::add`, fails with `SPAWN_FAILED`
and the adapter's S501, which names the roots and links its entry. The error names the command that
restarts the adapter, `iii call compose::restart worker=sandbox-libkrun`, which restarts each VM
worker it was running and ends every sandbox. The worker that failed was not running, so start it
afterwards with `iii call compose::up`. Or move the worker under the compose directory.

## Supported hosts

`sandbox-libkrun` is built for three targets:

* macOS on Apple Silicon (`aarch64-apple-darwin`)
* Linux with glibc on x86\_64 (`x86_64-unknown-linux-gnu`)
* Linux with glibc on aarch64 (`aarch64-unknown-linux-gnu`)

On Linux, the user who runs Compose needs read and write access to `/dev/kvm`, and `libcap-ng0` must
be installed. A project that needs the adapter on a musl Linux or Intel Mac host fails with
`UNSUPPORTED_PLATFORM`. 0.25 shipped `iii-worker` for musl Linux, so Alpine hosts lose bundle and
sandbox support in 0.26. Windows has no VM: a bundle worker there still fails with
`BUNDLE_NEEDS_A_VM`.

## Upgrade the binary

The first `iii update` from 0.25 still runs 0.25 code. It installs 0.26 to `~/.local/bin/iii`,
then prints `Release asset not found for platform <target>: iii-init` (and the same for
`iii-worker`) and exits 1. That is expected: the 0.26 release has neither asset.

Check that your shell now runs 0.26 with `iii --version`. If it still prints 0.25, the `iii` on your
`PATH` (`command -v iii`) is not `~/.local/bin/iii`, for example a Homebrew install in
`/opt/homebrew/bin`. Running `iii update` from it only repeats the errors above. Upgrade that copy
with `brew upgrade iii`, or remove it and put `~/.local/bin` on your `PATH`.

Once `iii --version` prints 0.26, run `iii update` again. It finishes cleanly and lists the leftover
binaries. It deletes nothing, so remove them yourself:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
rm ~/.local/bin/iii-worker ~/.local/bin/iii-init
```

To roll back, `VERSION=0.25.0` with the install script installs only `iii`: the script no longer
fetches `iii-worker` or `iii-init` for any release. Download `iii-worker-<target>.tar.gz` and
`iii-init-<target>.tar.gz` for your host from the
[iii/v0.25.0 release](https://github.com/iii-hq/iii/releases/tag/iii/v0.25.0) and put both
binaries next to `iii` in `~/.local/bin`. Without them, 0.25 runs no VM workers and no sandbox.

Errors from a 0.25.x engine carry a `docs_url` that points to
`crates/iii-worker/src/sandbox_daemon/README.md` on GitHub. That file is gone. The S-codes now live
on [Sandboxes](https://iii.dev/docs/next/creating-workers/sandboxes.md).

## Clean up VM state

0.25 kept each VM worker's state in `<state dir>/vm/<key>/`, where `<state dir>` is
`<compose-dir>/.iii/compose/<namespace>/`, or the directory under `$III_COMPOSE_STATE_DIR` when you
set it. Nothing reads these directories now, so delete them. The first boot of each VM in 0.26
starts from fresh state and reinstalls its dependencies.

`iii-worker` also left the paths below. `sandbox-libkrun` reads none of them, so delete them too:

* `~/.iii/managed/`, the state of each VM worker it ran.
* `~/.iii/workers/` and `~/.iii/workers-bundle/`, the binary and bundle workers it installed.
* `~/.iii/cache/bundles/`, the bundle archives it downloaded.
* `~/.iii/logs/` and `~/.iii/pids/`, its worker logs and PID files.
* `~/.iii/lib/iii-init`.
* `~/.iii/rootfs/` and `~/.iii/images/`, left by releases before 0.25.

Keep the rest of `~/.iii/lib/`: `sandbox-libkrun` still loads `libkrunfw` from there. Keep the rest
of `~/.iii/cache/` too. It holds pulled images, where 0.25 put them, so those are not pulled again.

The state is now in `~/.iii/sandbox/workers/<vm_id>/`, owned by the adapter. Compose deletes it
when you remove the VM container, also after `compose::down`: it starts the declared adapter for
the call. Two cases leave it behind for you to delete by hand:

* The adapter could not delete it: it failed to start, the compose file does not declare it and it
  is not running, or the engine had already stopped. The warning names the container, the adapter,
  and the `vm_id`, and says where the state stays.
* You moved or renamed the project directory, or changed `III_COMPOSE_STATE_DIR`. Every VM then
  starts from fresh state, and the directories of the old location remain in
  `~/.iii/sandbox/workers/`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.