Plan limits
- The box cap counts boxes, awake or asleep. At the cap,
make,exec, anddevfail withbox_limit.tesser rman idle box or upgrade. - An admin sets the spend limit with
tesser spend-limitor on the dashboard’s Billing tab. At the limit, the org’s awake boxes sleep, and launches and wakes fail withcredit_exhausteduntil the limit is raised or the month resets. - Pool boxes belong to no repo. Any
make,exec, ordevin the org claims one of its size.
Snapshots
What a box gets from one
- A snapshot is a disk image of a repo taken after every service’s
setupran. It holds/home/ubuntu,/var/lib/docker, and/var/lib/containerd. - A repo’s current snapshot is the newest ready one of a commit on the default branch. Every new box in the repo starts from it.
- A claimed pool box mounts the snapshot at claim time and starts while the rest fills in. If the mount fails, the box starts without it.
- The box reads the image as it needs it while the rest streams in, so it does not wait for the whole disk.
- The first sync sends only what changed since the snapshot’s commit, and
package stores and
node_modulesare already in place, so the first install is incremental. Without a snapshot, the firstsetupinstalls from scratch. Boxes never copy installs from each other. - Anything
setupinstalled outside/home/ubuntuand Docker’s data is not on the new box, except the manifest’saptpackages, which the box reinstalls on every start. - If a box cannot read its snapshot, it starts on an empty home directory.
tesser dev --snapshot <snapshot_id>starts a new box from a specific snapshot of the repo.tesser dev --no-snapshotstarts one without any.
Seeing which snapshot a box has
tesser ls --jsonshows the snapshot each box started from.tesser status <box_id>prints it on asnapshotline as<snapshot_id>@<sha> (<age> old), with the first 12 characters of the commit, or-for a box without one. A claimed pool box shows-until its snapshot is mounted.
Building one
tesser snapshot create builds a snapshot of HEAD, or of --sha <commit>:
- A builder box launches at the largest size any of the repo’s services uses, starting from the repo’s current snapshot when there is one.
- It syncs the commit and runs each service’s
setupwith the values fromtesser env set. - It 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 reach other people’s boxes. - It captures the disk.
- A repo builds one snapshot at a time, and a build that runs past its time limit fails.
- The builder needs room under the org’s box cap.
--waitstreams the setup log from the builder over SSH until the snapshot is ready or fails. The log stays on the builder, so--waitis the only way to read it. Without--wait,tesser snapshot show <snapshot_id>shows the build’s state.
Automatic builds from tesser dev
When tesser dev runs for a service without --snapshot or
--no-snapshot, it checks the repo’s snapshots once it has a box, against the
default branch’s tip as your clone last fetched it (origin/HEAD, else
origin/main, else origin/master).
- If that commit needs a snapshot, it starts
tesser snapshot createin the background and printstesser: building a snapshot of <repo> at <sha> in the backgroundon stderr, with the path of its log (snapshot-build.login the state directory). tesser devdoes not wait for the build, and Ctrl-C does not stop it.- It builds nothing when:
- no service has a
setupat that commit - a snapshot of the repo is already building
- the repo has a current snapshot that is not stale
- the last build at that commit failed (it tries again once the tip moves)
- the newest ready snapshot of the default branch is at a commit the tip does not contain
- no service has a
- A background build leaves room for one more box under the box cap. It stops quietly when the org has no room, is out of credit, has reached a plan limit, or keeps no snapshots.
tesser dev waits for the server and setup took 3 minutes or more,
it prints a hint after the box id:
- If the service has no shared instance, it names the
tesser make <service> --ensure-running <sha>command that would share one box’s setup with everyone. - If the default branch’s tip needs a snapshot, it names the
tesser snapshot create --sha <sha>command that builds one, or says one is already building.
Listing, staleness, and removal
- Inside a repo,
tesser snapshot lslists that repo’s snapshots and printsthis repo (<repo>): nonewhen it has none. Outside a repo, it lists the org’s.--alllists every repo’s, and--removedincludes removed and purged ones. - A snapshot of a commit not on the default branch is marked
branch. New boxes start from it only with--snapshot. tesser env setmarks the repo’s snapshotsstale, because their setup ran with the old values. A stale snapshot is still used until a new one is built.- Tesser keeps the newest ready snapshot of the default branch and the newest
of other branches, and deletes older ones once no box is reading from
them.
tesser snapshot rmremoves one sooner, and boxes made from it keep running. - An org that runs boxes in its own AWS account keeps snapshots in its own S3 bucket and has none until it names one.
Sleep and wake
What counts as idle
A box is in use while any of these is true:- a visible browser tab is showing it
- something other than a browser sends it requests (curl, a script, a test run)
- a CLI command targets it
- an SSH session is open where someone is typing, or something is running or printing
- Traffic between boxes does not count on its own, but a box in use keeps the boxes it depends on awake, so a server and its database idle together.
- A kept box keeps the boxes it depends on awake while its server runs. Once its server stops, they go back to their own idle clock.
- When an instance box starts or wakes, it wakes the boxes it depends on that you own or that are the org’s shared instances. A dependency wired to a teammate’s box is never woken this way.
What survives sleep
- A sleeping box keeps its disk and its id. Any command that targets it wakes it first, and opening it on localhost shows a Wake button.
- Sleep is hibernation: memory is written to disk, and on wake processes
carry on where they stopped. A dev server keeps its state and does not run
setupagain. - After a wake, boxd waits for the health check (see Timers).
A server that does not answer in time is started again from
setup. - Connections between boxes close on wake, so your app has to reconnect to its dependencies.
- These boxes get a plain power-off instead, and their dev server starts
again on wake:
- a box still streaming in its snapshot
- a box put to sleep in its first minute
- a box whose image or AWS settings do not support hibernation
Finding and inspecting boxes
tesser ls
- Piped, one line per box: kind, service, size, power state, whether it is online, when it was made, its IP address, and the local worktree it was made from.
- In a terminal, a live table with service, worktree, owner, size, state,
created, idle, and box columns. Press a column’s number or click its header
to sort, press
/to fuzzy search, and click a box twice to use it on localhost. - A worktree shows as its folder name, followed by its repository in
parentheses when the two differ, such as
src (zulip). The repository name is the last part of the worktree’soriginURL, or the repository’s folder when there is noorigin. tesser status <box_id>adds the instance id, IP addresses, and when it was last active.
tesser ssh and tesser exec
When a command after -- exits 127 and its command word still has quotes or
spaces in it, both print one extra line on stderr explaining why. The exit
code is passed through unchanged.
tesser util
- The box samples CPU and memory every second and reports a summary each minute.
- Each row is one step: mean, 95th percentile, and peak CPU as a percentage of all the box’s vCPUs, plus mean, peak, and total memory in GiB.
--sincetakes values like30m,12h, or90d, and defaults to an hour. Longer windows use wider steps so the output stays at 360 rows or fewer.- History is kept 90 days and is gone once the box is removed.
--jsonalso includes CPU and memory pressure.