.claude/skills/devboxes/, one per service. The filename is the
service name. .claude/skills/tesser/, .agents/skills/devboxes/ and
.agents/skills/tesser/ are read too; manifests in two of them is an error.
The smallest manifest
ports lists the ports the app listens on. setup prepares the box and runs
before every dev, so it needs to be safe to run repeatedly, and dev starts
the server. With this file in place, tesser dev web does everything else.
The manifest reference describes every field. The ones most
projects use are:
root = "apps/web", for a monorepo, so commands run inside the package directory.[health] path = "/healthz", when a port being open is not enough to know the app is ready.[deps], when this service calls another one. See Connect a frontend to a backend.[env], for env files and the names of required secrets, described below.
Starting, restarting, and stopping
tesser dev web creates or reuses the box for this worktree and service,
syncs the worktree, runs setup, starts dev, waits until the port answers,
and prints the box id. Running it again replaces the server, which is also
how you recover from a crashed one. The box runs the manifest it was synced
and nothing else; a one-off command is tesser exec.
tesser sync <box_id> pushes your edits so the server’s hot reload can pick
them up. tesser sync <box_id> --restart pushes the edits and then runs
setup and dev again, which you need for changes the running server cannot
take on its own, such as env changes, config changes, or a new dependency.
tesser logs <box_id> -f follows the server’s output.
Env files
Sync follows.gitignore, so a file like .env.local is not copied to the
box. When a dev server fails because a variable is missing, this is usually
the reason. There are two ways to handle it.
The first is to list the file in the manifest, after which every sync
includes it:
tesser env push <box_id>, which sends
every gitignored .env* file at the worktree root, or the specific files you
name. After a push, run tesser sync <box_id> --restart so the server starts
with the new values.
Secrets for shared instances
A shared instance is the org’s copy of a service that teammates’ boxes connect to (see Connect a frontend to a backend). It has no laptop attached, so its env values come from the org instead:env.required = ["DATABASE_URL"] makes a missing
value a clear error at start time instead of a crash later.
Several services in one repository
Each service gets its own file.tesser dev api and tesser dev web then
create two boxes from the same worktree, and if web.toml lists api as a
dependency, tesser wire <web_box> api <api_box> points the web box’s api
port at your api box instead of the org’s shared instance (see
Connect a frontend to a backend). When a repository has more than one
manifest, dev and make require the service name; when it has exactly one,
that service is the default.
Service names are shared across the whole org, so api refers to the same
service in every repository. If a second repository tries to register a name
that the first already owns, the registration fails with an error.