> ## 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 your app on a box

> Describe how to run your app in one small TOML file per service

A service is anything that listens on a port, such as a web app, an API, or a
worker with a health endpoint. Tesserc learns about your services from TOML
files in `.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.

```text theme={"theme":"css-variables"}
.claude/skills/devboxes/
├── web.toml     # service "web"
└── api.toml     # service "api"
```

## The smallest manifest

```toml theme={"theme":"css-variables"}
# .claude/skills/devboxes/web.toml
ports = [3000]

[run]
setup = "pnpm install"
dev   = "pnpm dev"
```

`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](https://tesser.sh/docs/manifest.md) 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](https://tesser.sh/docs/wiring.md).
* `[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 leaves a running server alone and
only syncs, because the server's hot reload picks up your edits. It starts
the server again when the server is not running or its app crashed under a
file watcher, which is how you recover from a crash, or when the manifest,
the env, or a file `setup` depends on has changed. Those files are the env
files the manifest lists, any
`package.json`, lockfiles, toolchain pins such as `.nvmrc`, a `Dockerfile` or
compose file, and any file the `setup` command names. 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 dev` does not return until the primary port is healthy, and it always
exits on its own, so run it in the foreground or wait for it to exit instead
of polling. While it waits, it prints the last line of the server's log every
30 seconds. It exits 1 and prints the end of the log when the run dies before
the port listens, when the app crashes under a file watcher such as
`nodemon`, `nest --watch`, `node --watch`, or `tsx watch` (the watcher keeps
running but waits for a file change, so the port never opens), or when
another process already holds the port. When the health check keeps
returning the same 5xx status, the progress line says how long it has done
so. When `setup` prints nothing new for 5 minutes, `tesser dev` prints a
warning with its last line and keeps waiting. Raise `[health] timeout` in the
manifest only when the last line still shows progress, not an error.

`tesser dev --detach` and `tesser sync <box_id> --restart --detach` return
once the run starts instead of waiting for the port. To wait for that run
afterwards, run `tesser wait <box_id>`. It waits the same way `tesser dev`
does, including the services the box is wired to, and exits 0 once the
primary port is healthy or exits 1 with the reason and the end of the log.
It also picks up a run whose `tesser dev` timed out. With no argument it
waits on the worktree's one dev box, and with a service name it waits on this
worktree's box for that service. `--timeout` (for example `--timeout 10m`)
replaces the manifest's `[health] timeout`. Use `tesser wait` rather than a
`curl` loop on the box's URL, which cannot tell a failed setup from a slow
one.

`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:

```toml theme={"theme":"css-variables"}
[env]
files = [".env.local"]
```

The second is to push it once with `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](https://tesser.sh/docs/wiring.md)). A shared
instance has no laptop attached, so its env values
come from the org instead:

```sh theme={"theme":"css-variables"}
tesser env set api DATABASE_URL=postgres://… STRIPE_KEY=sk_…
tesser env ls api
```

A secret typed on the command line ends up in shell history. Name the key
bare to read the value from stdin instead, or load a whole file:

```sh theme={"theme":"css-variables"}
tesser env set api STRIPE_KEY < ~/.stripe-key
tesser env set api --from-file .env.production
```

Tesserc stores the values and puts them in the environment of every shell
and dev server on the service's boxes. A workbench made from the same
repository gets every value the repository's services agree on, so a
private package install works there too; a key two services set differently
is left out. The values never appear in the manifest, which is committed to
the repository. Listing a
variable in the manifest as `env.required = ["DATABASE_URL"]` makes a missing
value a clear error at start time instead of a crash later.

Names that can run code on the box are refused, both by `env set` and in
`env.required`: `PATH`, `HOME`, `SHELL`, `NODE_OPTIONS`, `NODE_PATH`,
`HTTP_PROXY`, `HTTPS_PROXY`, `CC`, `RUSTFLAGS`, `GOPROXY`, and similar
interpreter, compiler, and proxy settings, plus anything starting with `LD_`,
`DYLD_`, `BASH_`, `GIT_`, `SSH_`, `NPM_CONFIG_`, `PNPM_`, `YARN_`, `BUN_`,
`COREPACK_`, `PYTHON`, `PERL`, `PIP_`, `BUNDLE_`, or `CARGO_`. A few harmless
Python settings such as `PYTHONUNBUFFERED` are allowed.

## 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](https://tesser.sh/docs/wiring.md)). When a repository has more than one
manifest, `dev` requires the service name; when it has exactly one, that
service is the default. `make` without a service name always creates a
workbench.

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.


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