Skip to main content
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). 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.

What moved

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:
worker-compose.yaml
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. 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.

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:
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. 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:
config.yaml
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:
worker-compose.yaml
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:
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:
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 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.

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/.