A box is a hosted unotest installation for one organisation. It runs the same viewer you use locally, in front of your own environments, and adds what a team needs around it: sign-in with your identity provider, roles, seats, an audit trail, scheduled runs and a place for CI to push suites to.
You rent a box yourself — cd unotest && npx @unotest/web box create, or Create box
in the dashboard — and pay for it by the hour from a prepaid balance
(prices); the steps are in
Quick start → Run it on a box.
From the CLI, renting a box, deleting one and turning its Picker on wait for
a person: the key from login cannot spend or delete by itself, so the
command prints where to approve and waits until somebody presses Approve
on the dashboard’s Approvals tab. A request nobody answers expires in ten
minutes, with nothing created and nothing charged.
unotest can also provision one for you under your own domain and sign-in —
see What you receive.
A box has a lifetime. At its end the box is deleted by itself and
charging stops, so a box nobody remembered does not burn the balance. A new
box gets 24 hours unless you choose otherwise — from @unotest/web 0.55.0
on, box create --ttl 8h (hours or days, up to 30d), or --ttl none for
a box that runs until it is deleted, which is what a CI box wants.
box extend <name> --ttl 4h moves the end later and charges nothing at
once. box status, box list and the dashboard show when a box ends, next
to its price; in the last hour box status prints the command that extends
it. The owner gets a letter when the lifetime ends the box. Boxes rented
before lifetimes existed have none and keep running while they are paid for.
Your tests never leave your repository except by your own action: a box
holds no credential to your code and never pulls anything. Suites reach it
through bundle push. To run them on every push,
npx @unotest/web init --ci github writes the CI workflow — see
Run on every push.
What is inside
| Component | What it does |
|---|---|
| Guard | Sign-in, roles, seats, the licence check, the administration pages and the audit trail. Every request to the box passes through it. |
| Viewer | One viewer per environment, in a container of its own. The same UI as cd unotest && npx @unotest/web viewer, with the controls your role does not permit greyed out. It sees the environment’s bundle and run history and nothing else of the box. |
| Run sidecar | The only component that holds the docker socket. Every run — ordered from the viewer, from CI, or by a schedule — and every bundle’s npm ci executes in a throwaway container it starts for that one job. |
| Daemon | Stores pushed bundles, holds each environment’s values and secrets, runs the per-environment queue and the schedules, and answers CI. It executes none of your code: it asks the sidecar for a container. |
| Updater | Installs the version your box is set to, verifying every artifact against a signature before it is installed and rolling back if the stack does not come up healthy. It never picks a version of its own — see Updates. |
Projects and environments
A box serves one or more projects, each with one or more
environments (test, staging, prod, …). An environment has a
target — the URL the tests run against, injected into every run as
APP_BASE_URL — plus its own variables, secrets, run history, queue and
viewer.
A box runs only your tests. It does not start your app: every target
must be an address the box reaches over the internet. localhost, a
private network (10.x, 172.16–31.x, 192.168.x), a link-local address
or a name only your network resolves (*.local, a compose service name)
cannot work there — see
What a box cannot reach.
The box’s configuration lists the environments; installing a bundle fills
them. The list is set when the box is made: box create takes your
project’s own environments — the unotest/.env.<name> overlays you run
locally, or default when there are none — the dashboard asks for an address
and the environment names on it, and a box unotest provisions for you gets
the list you agree on. A pushed suite cannot create or rename an environment.
An environment gets its viewer once a bundle is installed for it.
bundle push uploads the suite; the newest bundle of the environment’s
branch becomes its bundle, the box installs it, and the viewer opens on it
(see Pushing suites). Until
then the environment is listed with no viewer.
Pull requests can get environments of their own (pr-<n>), created and
removed automatically when a project has preview deployments — see
Pushing suites.
Updates
A box never updates itself. A new release is offered, not installed: your box keeps running the version it is on until somebody presses Update.
Your box’s card in the cabinet shows both versions — what it runs and what is on offer — with an Update button when they differ. The card is also where you see the result: the box picks the new version up, installs it, and reports back. How soon it picks it up depends on the box: one whose updater checks for a new target every five minutes takes minutes, one still on the hourly check takes up to an hour and a half. A box waits that long only until it has the five-minute check — the update that brings it is the last slow one. If the stack does not come up healthy the updater rolls the box back to the version that worked, and the card says so with the reason; the update is not retried until you press again.
An agent does the same over the REST API: GET /api/v1/boxes/<name> carries
an update object with what is installed, what is available and where the
box is between the two, and POST /api/v1/boxes/<name>/update with
{"version": "<the version you were offered>"} presses the button. The
version is named on purpose — if a newer one appeared while you were
looking, the call is refused with 409 update-stale and the version that
is on offer now, rather than installing something nobody chose.
cd unotest && npx @unotest/web box status prints the same three facts.
Balance and deletion
A box is paid for from your prepaid balance, one started hour at a time.
When the balance cannot cover a box’s next hour, that box is deleted. The
moment that will happen, if nobody tops up, is computed from the balance
and every box on the account, each at its own rate: fundedUntil on a box
in GET /api/v1/boxes/<name>, and on the account in GET /api/v1/balance.
The paidUntil beside it is only the end of the hour already paid for —
with credit left, the next hour is simply bought.
Where your code runs
Nothing of your suite executes next to the box’s own state. A run gets a
container of its own: the bundle’s files read-only, the environment’s run
history read-write, the target, variables and secrets of that one
environment — and no view of the daemon, of other environments, of tokens
or of channel credentials. The container is gone when the run is over. A
bundle’s npm ci runs the same way, so a dependency’s install script
builds a native module without touching the box. The viewer of an
environment is a long-lived container on a network shared only with the
guard and the daemon, with no route out of the box.
Two consequences for the UI on a box: editing a suite’s files is not
available — the sources are read-only there, and a push is what changes
them (such edits never survived the next bundle push anyway) — and
Debug cannot attach to a run, which lives in a container the viewer
has no path into. Details in Security model.
What the container holds — which paths a suite may write, the variables it
is given, where a shell() step starts — is on
What a run gets.
What you receive
A box you rent yourself gets its address and tokens written into your
project by box create. When unotest provisions a box for you:
-
A domain —
https://tests.example.com, or a name under your own domain. Sign-in redirects, the CI webhook and the viewer all live there. -
A licence — signed, with your organisation’s name, a seat count and an expiry date. Seats are counted against people who have signed in recently; see Sign-in.
-
Project tokens — for
bundle pushand CI, one per project. Tokens that may also write environment values are minted separately. -
A Picker, if you want one — the grounder that resolves intent locators while your agent writes tests, billed per hour on top of the box. It serves the MCP server on your own machine, not the runs on the box.
box create --pickerquotes what it costs before anything is charged, and what it reads is on The Picker.
You provide the identity-provider details (an OAuth client, and for Google Workspace a directory service account) and the mapping from your groups to roles. Nothing about your Workspace is stored anywhere but on your box.
Roles
Two roles exist:
- admin — run tests, open the terminal, switch an environment’s bundle, administer users, see and set environment values.
- readonly — browse scenarios, runs and results. Run and the terminal are unavailable, and the proxy refuses them regardless of what the UI shows.
Roles come from your directory groups, and an administrator can pin one on the box. Details in Sign-in and Administration.
Next
- Sign-in — SSO, groups → roles, break-glass, seats
- Administration — users, sessions, the audit trail
- Environments & values — targets, variables, secrets
- Pushing suites —
bundle pushfrom a laptop or CI - Schedules & queue
- The Picker — grounding intent locators, modes, the ceiling
- Security model
- Troubleshooting