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

# Run tests, builds, and commands on a box

> What a box is, the two kinds, and what happens to a box you stop using

A box is a Linux machine in the cloud that holds a copy of your worktree at
`~/workspace`. Tesserc creates boxes, syncs your worktree to them, runs
commands on them, and puts them to sleep when they are idle. You do not deal
with instance ids, SSH keys, or IP addresses; `tesser status` shows the
instance id and IP addresses if you want to see them.

Each box has 2 vCPU, 8 GB of memory, and a 40 GB disk by default. `--size`
picks small (1 vCPU, 4 GB) through xlarge (8 vCPU, 32 GB), with large and
xlarge on Pro and Team. Every box runs Ubuntu 24.04 with Node (22 and 24, via fnm), Bun, rsync, and Docker (compose
and buildx included) installed. The `ubuntu` user has passwordless sudo and is in the
docker group, so `sudo apt-get install` and `docker compose up` both just work.
You can check what else is available with `tesser exec -- which pnpm` and
install what your project needs with `tesser exec`.

## Two kinds of box

A **workbench** holds your worktree and runs nothing on its own. It is the box
`tesser exec` uses when you do not name one, and it is where tests,
typechecks, builds, and one-off scripts run. Each worktree gets a workbench
the first time it needs one.

An **instance box** runs one service's dev server. `tesser dev <service>`
creates one for each combination of worktree and service, so a repository
with a frontend and a backend ends up with two instance boxes and one
workbench.

Keeping these separate means a test run never competes with the dev server
for CPU, and an install on the workbench never replaces the dev server's
`node_modules` while it is running.

## Creating a box

Most of the time you do not create boxes directly, because `exec` creates the
workbench and `dev` creates the instance box. `tesser make` exists for when
you want a box before running anything on it: `tesser make` creates a
workbench and `tesser make <service>` creates an instance box. Both print the
new box id.

When a warm box of the requested size is available in the pool, creating a
box takes a second or two. Otherwise a new machine is launched, which takes
about 90 seconds. `tesser pool target` keeps a couple of warm boxes
of a size ready, and lets them expire an hour after the last box they could
serve. Pool boxes belong to no repo: any `make`, `exec`, or `dev` in the org
claims one of its size.

Every org has a box cap from its plan: 3 boxes on Hobby, 10 on Pro, none on
Team. When `make`, `exec`, or `dev` fails with `box_limit`, remove an idle box
with `tesser rm` or upgrade. `tesser sleep` does not help: the cap counts
boxes, not awake ones.

Pro and Team bill usage past what the plan includes. An admin can cap that
with `tesser spend-limit 50` or on the dashboard's Billing tab: when the org
reaches it, its awake boxes sleep, and launches and wakes fail with
`credit_exhausted` until the limit is raised or the month resets.

## Snapshots

A snapshot is a disk image of a repo taken after every service's `setup`
has run. When a repo has one, every new box made from it starts from the
repo's current snapshot instead of an empty home directory. A pool box gets
the snapshot when it is claimed: Tesserc mounts it on the claimed box, and the
box starts from it while the rest fills in. If the mount fails, the box
starts without the snapshot. The current snapshot is the
newest ready one of a commit on the default branch. The box reads the image
as it needs it while the rest streams in behind, so it does not wait for the
whole disk. The first sync then sends only what changed since the snapshot's
commit. Its package stores and `node_modules` are already in place, so the
first install is incremental. Without a snapshot, a new box's first `setup`
installs from scratch. Boxes do not copy installs from each other.
`tesser ls --json` shows which snapshot each box started from.
`tesser dev --snapshot <snapshot_id>` starts a new box from a specific
snapshot of the repo instead, and `tesser dev --no-snapshot` starts one
without a snapshot.

Run `tesser snapshot create` inside the repo to build a snapshot of HEAD, or
of another commit with `--sha <commit>`. Tesserc launches a builder box at the
largest size any of the repo's services uses, starts it from the repo's
current snapshot when there is one, syncs the commit, and runs each service's
`setup` with the values set by `tesser env set`. It then captures
`/home/ubuntu`, `/var/lib/docker`, and `/var/lib/containerd`. Before the
capture, the builder deletes `~/.tesser`, `~/.ssh/authorized_keys`,
`~/.ssh/known_hosts`, `~/.bash_history`, `~/.gitconfig`,
`~/.git-credentials`, `~/.netrc`, `~/.config/tesser`, `~/.config/gh`,
`~/.aws`, `~/.claude`, `~/.claude.json`, and `~/.codex`, so no one's
credentials end up in other people's boxes. A repo builds one snapshot at a
time, a build has 60 minutes to finish, and the builder needs room under the
org's box cap. `--wait` follows the build and streams the setup log from the
builder over SSH until the snapshot is ready or fails. The setup log stays
on the builder, so `--wait` is the only way to see it. Without `--wait`,
`tesser snapshot show <snapshot_id>` shows the build's state.

`tesser dev` also builds a missing snapshot for you. When you run it for a
service without `--snapshot` or `--no-snapshot`, it checks the repo's
snapshots once it has a box. It compares them against the tip of the default
branch as your clone last fetched it (`origin/HEAD`, or else `origin/main` or
`origin/master`). If that commit needs a snapshot, `tesser dev` starts
`tesser snapshot create` for it in the background and prints
`tesser: building a snapshot of <repo> at <sha> in the background` on stderr,
with the path of its log, `snapshot-build.log` in the
[state directory](https://tesser.sh/docs/config.md). `tesser dev` does not wait for the build, and
pressing Ctrl-C does not stop it. It builds nothing when no service has a
`setup` at that commit, when a snapshot of the repo is already building, or
when the repo has a current snapshot that is not stale. It also builds nothing
when the last build at that commit failed (it tries again once the tip moves),
or when the newest ready snapshot of the default branch is at a commit the tip
does not contain. A background build also leaves room for one more box under
the org's box cap. It stops quietly when the org has no room for it, has run
out of credit, has reached a plan limit, or keeps no snapshots.

`tesser snapshot ls` lists the org's snapshots. A snapshot of a commit that is
not on the default branch is marked `branch`, and new boxes start from it
only with `--snapshot`. `tesser env set` marks the repo's snapshots `stale`, because their setup
ran with the old values. A stale snapshot is still used until you build a new
one. Tesserc keeps the newest ready snapshot of the default branch and the
newest of other branches, and deletes older ones once no box is still reading
from them. `tesser snapshot rm <snapshot_id>` removes one sooner; boxes
already made from it keep running. If a box cannot read its snapshot, it
starts on an empty home directory instead.

An org that [runs boxes in its own AWS account](https://tesser.sh/docs/self-hosting.md#snapshots)
keeps snapshots in an S3 bucket of its own and has none until it names one.

## Syncing

Every `exec` and `dev` copies your worktree to the box before running. Only
changed files are transferred, and `.gitignore` is respected, so
`node_modules`, `.env.local`, and build output stay on your laptop. The box
gets its own `node_modules` when the `setup` command runs. If a sync would
delete most of what is on the box, Tesserc stops and reports it, because that
usually means the box was created from a different worktree. In that case,
create a new box rather than passing `--force`.

The copy on the box is a real git repository with a single commit, your
current HEAD. `git status` and `git diff` work there, and `git log` shows one
entry. Anything you commit on the box is lost at the next sync, so always
commit on your laptop.

## Sleep, wake, and removal

Idle boxes go to sleep on their own. A sleeping box keeps its disk and its id
and costs a few cents a day. Any command that targets a sleeping box wakes it
first, which takes about a minute. Opening a sleeping box on localhost shows a
Wake button that does the same.

Tesserc hibernates a box when it sleeps: the box writes its memory to its disk,
and on wake its processes carry on where they stopped, so a running dev server
keeps its state and does not run `setup` again. After the wake, boxd waits up
to 60 seconds (or the service's `[health] timeout`, if shorter) for the server
to answer its health check. A server that does not answer is started again
from `setup`. Connections between boxes are closed on wake, so your app
reconnects to its dependencies. Some boxes get a plain power-off instead, and
their dev server is started again on wake: a box that is still streaming in
its [snapshot](#snapshots), a box put to sleep in its first minute, and a box
whose image or AWS settings do not support hibernation.

| | Workbench | Instance box |
| - | - | - |
| Sleeps after | 10 minutes idle | 2 hours idle |
| Removed after | 16 hours asleep | 16 hours asleep |

`tesser sleep-after 30m` sets your own idle time for the instance boxes you
own in the org, anywhere from 5 minutes to 2 hours, or up to 24 hours on Pro
and Team; `--default` goes back to 2 hours. `tesser dev --sleep-after 30m`, or `tesser sleep-after 30m --box <box_id>`, sets one box's idle time instead, over its owner's.

A box counts as idle when nobody is using it. Using it means one of these:
a browser tab showing it that is visible, a request from something other
than a browser (curl, a script, a test run), a CLI command, or an SSH session
where someone is typing or something is running or printing. A background tab, or a
shell left open with nothing running in it, does not keep a box awake.
Traffic between boxes does not count on its own, but a box you are using
keeps the boxes it depends on awake, so a server and its database idle
together. When an instance box starts or wakes, it also wakes the boxes it
depends on, as long as you own them or they are the org's shared instances.
A dependency wired to a teammate's box is never woken this way.

You can also act immediately: `tesser sleep <box_id>` powers a box down, and
`tesser rm <box_id>` removes the box permanently. Neither asks for
confirmation.

A box that must never go down on its own, such as a database the rest of the
org depends on, is kept (on Pro and Team): `tesser keep <box_id>` takes it off the idle clock
entirely, so it neither sleeps nor expires, and bills for every second it is
awake. `tesser keep <box_id> --off` puts it back. Explicit `sleep` and `rm`
still work on a kept box. When a coding agent such as Claude Code or Codex
runs `tesser keep`, Tesserc refuses unless the agent also passes `--yes`, which
states that you asked for the box to stay up.

## Finding a box

`tesser ls` lists every box in the org. Piped, it prints one line per box
with its kind, service, size, power state, whether it is currently online,
when it was made, its IP address, and the local worktree it was made from. In
a terminal it opens a live table instead, with service, worktree, owner, size,
state, created, idle, and box columns: press a column's number or click its
header to sort by it, press `/` to fuzzy search, and click
a box twice to use it on localhost.
`tesser status <box_id>` shows the details
of one box, including its IP addresses and when it was last active. Both
accept `--json`.

## Looking inside a box

`tesser ssh <box_id>` opens a shell in `~/workspace`, and
`tesser ssh <box_id> -- <cmd>` runs a single command there without syncing
first. When you run it from a terminal, the command gets a terminal on the box
too. Use these for inspection rather than editing, since an edit made on
the box is overwritten by the next sync. To read the dev server's output, use
`tesser logs <box_id>` for the last 200 lines or `tesser logs <box_id> -f` to
follow it. `-n` sets how many lines to show. When the manifest's health check
is a `GET` on a path other than `/`, `tesser logs` leaves out the `GET`
request lines for that path, and `--health` keeps them.

Each `tesser dev` or restart starts a new log, and so does a run whose log
passes 10 MB. The box keeps the two logs before the current one, as
`~/.tesser/dev.log.1` and `~/.tesser/dev.log.2`. `--grep` takes an extended
regular expression and searches all three logs, oldest first, prefixing lines
from the older logs with their file name. It exits 1 when no line matches:

```sh theme={"theme":"css-variables"}
tesser logs <box_id> --grep 'error|warn' -n 50
```


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