~/workspace. Tesser 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 will show them
if you want to see them.
Each box has 2 vCPU and 8 GB of memory by default — --size picks small (1 vCPU, 4 GB) through xlarge (8 vCPU, 32 GB), with large and xlarge on Pro and Team — with a 40 GB disk, running
Ubuntu 24.04 with Node (22 and 24, via fnm), Bun, rsync, and Docker (compose
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 boxtesser 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, becauseexec 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. On Team, tesser pool target, run inside a repo, keeps a couple of
warm boxes for that repo ready, and lets them expire an hour after the last box
made there. Only a box made from that repo claims one.
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.
A brand-new box also warms itself from its siblings automatically: right
after it boots, it copies the pnpm, npm, yarn (classic and berry), bun, cargo,
go, and uv stores from up to three awake boxes in the org, over the private
network. When one of those boxes was made from the same repo — any service
or workbench — every node_modules in its workspace comes along too, so the
first npm install is the incremental one — seconds, not the cold install.
With bun or pnpm, whose node_modules are hard links into the store, the
store is what comes across and the first install relinks it. A pool box kept
for a repo (tesser pool target run inside it) gets that repo’s
node_modules at launch, before anyone claims it, so dev there starts on an
install that is already in place; a blank pool box (--any-repo) gets them
when it is claimed. A box keeps packed copies of what it hands out, so a
sibling that has been idle for hours serves as fast as one that just
installed. The first dev or exec
on that box waits for the copy to finish (up to four minutes) and prints how
it ended, so an install never starts mid-copy. This is best-effort — if no
sibling is awake or the copy fails, the install simply runs cold — and it
never touches your code, env files, or anything outside the package stores
and node_modules. The node_modules come from whatever branch that sibling
has installed — same repo, not necessarily the same lockfile — which is
exactly what the incremental install reconciles; and unlike the stores, whose
contents the package manager verifies, they are trusted as-is from any box in
the org.
Syncing
Everyexec 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, Tesser 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 40 seconds, and if the box had a dev server running it is started again. Opening a sleeping box on localhost shows a Wake button that does the same.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.
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.
Finding a box
tesser ls lists every box in the org with its kind, service, power state,
whether it is currently online, when it was made, and the local worktree it
was made from. In a terminal it opens a live table instead: 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. 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.