Skip to main content
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. 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 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, a box put to sleep in its first minute, and a box whose image or AWS settings do not support hibernation. 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: