|
| 1 | +--- |
| 2 | +name: a3s-box |
| 3 | +description: Drive the a3s-box microVM sandbox CLI — a Docker-like runtime that runs OCI/container images in hardware-isolated microVMs (libkrun). Use when the user wants to run, build, exec into, snapshot, or tear down containers with a3s-box, sandbox untrusted or agent-generated code, spin up throwaway isolated environments, or mentions a3s-box / microVM / libkrun / "run this safely in a sandbox". Teaches the box lifecycle, the `--` argv separator, the networking model, and error recovery. Not for plain Docker/Podman (different CLI, same verbs). |
| 4 | +allowed-tools: Bash(a3s-box*), Bash(curl*), Read(*), Grep(*) |
| 5 | +--- |
| 6 | + |
| 7 | +# a3s-box — drive the microVM sandbox |
| 8 | + |
| 9 | +`a3s-box` runs OCI images inside hardware-isolated microVMs with a Docker-like |
| 10 | +CLI (verbs mirror Docker). **Preflight once:** `a3s-box info` (confirms |
| 11 | +virtualization + home dir). |
| 12 | + |
| 13 | +## Mental model (these cause silent failures) |
| 14 | + |
| 15 | +- **Lifecycle:** `created → running → paused → stopped → dead`. |
| 16 | + - `run` = create **and** start. `create` stops at `created` → `start` it. |
| 17 | + `snapshot restore` → `created` → `start` it. |
| 18 | + - A box dies when its **PID 1 exits** — it needs a foreground process. |
| 19 | + `run -d alpine` runs `sh`, which exits at once → `dead`. Use a long-lived |
| 20 | + command: `run -d nginx`, or `run -d alpine -- sleep 3600`. |
| 21 | + - `exec`/`shell`/`cp`/`top`/`attach` need a **running** box. `start` accepts |
| 22 | + only `created|stopped|dead`. `rm` removes `created`/`stopped`/`dead` without |
| 23 | + `-f`; `-f` is only for a `running`/`paused` box. |
| 24 | +- **The `--` rule:** the in-box command goes **after `--`**. |
| 25 | + `a3s-box exec <box> -- <cmd>` (required); `a3s-box run <image> [-- <cmd>]` |
| 26 | + (optional override). Missing it → `error: unexpected argument '…' found`. |
| 27 | +- **`exec` has a 5-second default timeout** — long builds/installs/tests get |
| 28 | + killed mid-run (and look like a failure). Use `--timeout 300` (or `--timeout 0` |
| 29 | + to disable): `a3s-box exec --timeout 300 web -- <cmd>`. |
| 30 | +- **Detached boxes need the monitor.** `run -d` prints the id and exits; its |
| 31 | + health/restart task dies with it. For `--restart` policies and health checks to |
| 32 | + fire, run `a3s-box monitor` in the background first. |
| 33 | +- **No in-guest localhost by default.** A box can't reach its own service on |
| 34 | + `127.0.0.1` (so `--health-cmd 'curl localhost'` and in-box curl fail). To make |
| 35 | + in-guest localhost work, attach a bridge network (recipe below). To check a |
| 36 | + service from your side, curl the **published HOST port** (see Ports). |
| 37 | +- **Ports:** publish with `-p HOST:GUEST` (TCP). But `ps`'s PORTS column and |
| 38 | + `a3s-box port <box>` render `GUEST -> 0.0.0.0:HOST` — the host port to curl is |
| 39 | + the one after `0.0.0.0:`. Read it with `a3s-box port <box>`. |
| 40 | +- **Output streams:** `run -d` prints a human `Creating box <name> (<id>)...` |
| 41 | + line, the full box id, and (when uncached) image-pull progress — all to |
| 42 | + **stdout**; only tracing/WARN/ERROR go to **stderr**. Don't parse the id from |
| 43 | + stdout — reference boxes by `--name`. JSON where offered: `inspect`, |
| 44 | + `image-inspect`, `snapshot ls --json`, and `ps --format json` (2.6+ only; on |
| 45 | + 2.0 use `ps --format '{{.ID}} {{.Status}} {{.Names}}'`). |
| 46 | + |
| 47 | +## Run → verify → exec → teardown |
| 48 | + |
| 49 | +```sh |
| 50 | +a3s-box info |
| 51 | +a3s-box run -d --name web -p 8080:80 app:dev # reference by --name, not stdout |
| 52 | +a3s-box ps -a --filter name=web # MUST use -a; expect STATUS=running |
| 53 | +a3s-box port web # host port = value after 0.0.0.0: |
| 54 | +curl -fsS http://localhost:8080/ # confirm SERVING from the host |
| 55 | +a3s-box exec --timeout 60 web -- env # in-box command after -- |
| 56 | +a3s-box logs web # logs print on stderr |
| 57 | +a3s-box stop web && a3s-box rm web |
| 58 | +``` |
| 59 | + |
| 60 | +A box can boot then die — always verify with `ps -a` (a `dead`/gone box does |
| 61 | +**not** appear in plain `ps`; empty output means dead-or-gone, not "name typo"). |
| 62 | +If STATUS=`dead`, its main process exited: read `a3s-box inspect web` |
| 63 | +(`.State.ExitCode`, summary `dead (Exit N)`) and `a3s-box logs web`. |
| 64 | + |
| 65 | +## Errors → fix |
| 66 | + |
| 67 | +| Error (on stderr) | Cause → fix | |
| 68 | +|---|---| |
| 69 | +| `error: unexpected argument '…' found` (Usage … `-- <CMD>`) | missing `--` → `exec <box> -- <cmd>` | |
| 70 | +| `Box X is not running` | stopped/created/dead → `a3s-box start X`; if just run, it died on boot → `inspect`/`logs X` | |
| 71 | +| `Box X is not running (status: dead)` | PID 1 exited → `a3s-box inspect X` (`.State.ExitCode`); run a long-lived command | |
| 72 | +| `No such box: X` | wrong ref → `a3s-box ps -a` to find name/id | |
| 73 | +| `WARN … heartbeat failed, exec will not be available` | guest booted but exec channel never came up → unhealthy; `logs X`, recreate | |
| 74 | +| `libkrun call failed status=-17 … krun_add_vsock_port2` / `VM boot failed` | started an already-running/stale box → `a3s-box ps`; if running just `exec`; if wedged `stop X` then `start X` | |
| 75 | + |
| 76 | +## Core commands (non-obvious; full verb list: `a3s-box --help`) |
| 77 | + |
| 78 | +| Goal | Command | |
| 79 | +|---|---| |
| 80 | +| Run one command, throwaway | `a3s-box run --rm alpine -- echo hi` | |
| 81 | +| Create then start | `a3s-box create --name w nginx` → `a3s-box start w` | |
| 82 | +| Exec / interactive shell | `a3s-box exec -it web -- /bin/sh` · `a3s-box shell web` | |
| 83 | +| List, custom columns | `a3s-box ps -a --format '{{.ID}} {{.Status}} {{.Names}}'` | |
| 84 | +| Build image | `a3s-box build -t app:dev .` | |
| 85 | +| Copy in / out | `a3s-box cp ./f web:/data/f` · `a3s-box cp web:/data/f ./f` | |
| 86 | +| Commit box → image | `a3s-box commit web app:snap` | |
| 87 | + |
| 88 | +Resource/isolation flags on `run`/`create` — `--cpus` (default 2), `--memory` |
| 89 | +(default 512m), `-e K=V`, `-v host:guest`, `--read-only`, |
| 90 | +`--cap-drop ALL --cap-add NET_BIND_SERVICE`, `--pids-limit`, `--network`, |
| 91 | +`--init`, … : `a3s-box run --help`. |
| 92 | + |
| 93 | +## Recipes |
| 94 | + |
| 95 | +**Working in-guest localhost (bridge network)** |
| 96 | +```sh |
| 97 | +a3s-box network create mynet --subnet 10.89.0.0/24 |
| 98 | +a3s-box run -d --name api --network mynet -p 8080:80 app:dev |
| 99 | +``` |
| 100 | + |
| 101 | +**Snapshot → restore** (filesystem snapshot; restored box lands in `created`) |
| 102 | +```sh |
| 103 | +a3s-box snapshot create web # create from a running/stopped box |
| 104 | +a3s-box snapshot ls --json |
| 105 | +a3s-box snapshot restore <snap-id> --name restored # name it → no ps scraping |
| 106 | +a3s-box ps -a --filter name=restored && a3s-box start restored |
| 107 | +``` |
| 108 | + |
| 109 | +## Finding exact flags & versions |
| 110 | + |
| 111 | +`a3s-box <command> --help` works for every command, and nested help works too: |
| 112 | +`a3s-box snapshot create --help`, `a3s-box network create --help`, etc. Check the |
| 113 | +build with `a3s-box version`; newer builds (≥2.4) add `pool run`, |
| 114 | +`snapshot prune`, and `monitor --install/--metrics-addr`. |
| 115 | + |
| 116 | +## More |
| 117 | + |
| 118 | +Other areas — `network`/`volume`/`compose`/`pool` (warm pre-boot VMs), registry |
| 119 | +`login`/`push`, `events`/`audit`/`df`, and TEE (`--tee`/`--tee-simulate`, |
| 120 | +`attest`/`seal`/`unseal`/`inject-secret`, `--sidecar`): `a3s-box --help`, then |
| 121 | +`a3s-box <cmd> --help`. |
0 commit comments