.env. Two of the defaults it ships are placeholders published in the repository, which is fine for the localhost test drive this page walks through and not fine for anything else. Secure the instance is where you replace them; Tessary warns on every boot until you do, and refuses to start the moment you point a real domain at it.
Handing this to a coding agent instead?
setup.md in the repository root is written as instructions to one, and stops at the URL — everything from creating an account onward is this page.Prerequisites
- Docker Compose v2.34 or newer (
docker compose version). Older versions either cannot read a compose file from a registry or hide it behind an experimental flag. - Docker Engine 26 or newer, for the named-volume subpath mount the sandbox runner uses.
- A free
tessaryCompose project name on your host (the configuration pins the project name totessary) - The group id of the Docker socket as containers see it, so the sandbox runner can reach it. The default is
0, which is what Docker Desktop uses on macOS and Windows — if that is you, there is nothing to set. On Linux, rundocker run --rm -v /var/run/docker.sock:/var/run/docker.sock busybox:1.36 stat -c %g /var/run/docker.sock; if it prints anything but0(a stock install usually prints thedockergroup’s id, often999), writeDOCKER_SOCK_GID=<that number>into a.envfile besidedocker-compose.ymlbefore starting. Getting this wrong shows up asEACCES /var/run/docker.sockin the sandbox runner’s log and an agentic RCA or triage run that never starts.
docker compose up -d runs the same configuration from docker-compose.yml in the repository root, and additionally supports --build and the observability profile, neither of which a registry reference can resolve.
Operating a stack you started from a registry
Once the stack is up, day-to-day commands find it by project name and need no configuration file:-f oci://docker.io/tessaryai/tessary:compose again only to start or upgrade the stack.
A .env beside the command still sets any variable the configuration names (HTTP_PORT, SITE_DOMAIN, the sealing keys, and the rest of Configuration’s table). What it cannot do is reach settings the configuration does not name: from a clone the file passes your whole .env into the backend container, and a published artifact deliberately carries no .env at all. For those, layer a small override file:
docker compose -f oci://docker.io/tessaryai/tessary:compose config > docker-compose.yml.
What comes up
docker compose up -d starts the following services:
For what every environment variable in the configuration does, see Configuration.
Start Tessary
1
Start the services
tessaryai/tessary:<service>-<version> — never a moving -latest tag — so this reference can
never hand you a configuration older than its own images. To hold a stack still, name the
release instead: oci://docker.io/tessaryai/tessary:compose-<version>, where <version> is a
published release. From a clone the images default to -latest and follow each release; set
TESSARY_VERSION in .env to pin all four together at once (see .env.example).2
Wait for it to be ready
-p tessary is how you reach the stack after the install: the project name is pinned in the configuration, so ps, logs and down all find it without naming a file.docker compose ps shows postgres, backend, frontend and sandbox-runner with healthy in their STATUS column, and sandbox-runner-work-init as exited (0). The backend takes the longest, under a minute on a laptop. Then open http://localhost (or https://<your SITE_DOMAIN> if you set one). The sign-up screen loads.What lands on your disk
Nothing but what Docker manages: the images, Compose’s cached copy of the configuration, and two named volumes.down on its own keeps both.
Secure the instance
Do this before the instance holds anything you care about, and before anything but your own machine can reach it. Sign-up is open by default: anyone who can reach the instance can create an account. That is what lets the first run need no admin bootstrap, and it is also why the instance must stay on your own machine or inside your own network until you have set the sign-up policy. Once your own account exists, go to Settings → Members → Sign-up policy and choose listed email domains or invitation only; the change takes effect immediately and never removes a member. Colleagues you invite from that page join your organization; anyone the policy merely admits gets an organization of their own. Both are described in Configuration. The repository’s exposure sweep (scripts/check-exposure-sweep.sh, which probes a fresh boot from outside) records the open sign-up endpoint on a fresh boot as by design for exactly this reason, and this paragraph is its documented posture.
Tessary ships with two sealing keys already set, which is why the steps above needed no configuration. They are placeholders, written in docker-compose.yml in the public repository, so they are not secrets and anyone can read them:
The backend logs a warning on every boot that runs either default (
docker compose logs backend shows Running on the shipped placeholder value), and refuses to start once SITE_DOMAIN names a real host, so an instance cannot go public on a published key by accident. Replace them:
1
Generate a value for each and put them in .env
.env.example carries both keys commented out with the same openssl recipe beside them (the first line keeps a .env you already have, such as one carrying DOCKER_SOCK_GID); the second line appends one generated value for each, and a later assignment wins. Set POSTGRES_PASSWORD to your own too if you publish the database port or share the host; it defaults to tessary and is otherwise reachable only from the Compose network.Without a clone, write the two keys straight into a .env in the directory you run the install command from. Compose reads it from there.2
Restart
docker compose logs backend no longer carries the placeholder warning.Sign up and connect your traces
Signing up and connecting a source is a two-step, gated flow: step 2 has exactly one way through it (a tagged span arriving) and one quiet escape hatch (a sample project), and nothing past it is reachable until one of those happens.1
Create your account
Create the first account with an email and password. The open edition authenticates through
PasswordAuthProvider by default, so no WorkOS or SSO configuration is required. Tessary creates your organization and a Default project during sign-up, so there is nothing to fill in for either.2
Connect your traces
You land directly on the connect gate — a full screen with no sidebar, since nothing else in the product is reachable yet. It shows the ingest endpoint (your own origin plus It prints
/v1/traces), a Bearer token already minted, and a one-line prompt written for a coding agent. The prompt points at instrument.md, which is the workflow your agent follows: read your repository and its own contributor instructions, show you the agent call sites it found and wait for you to confirm them, extend whatever tracing you already have rather than adding a second stack, stamp every model call’s span with a tessary.call_site.id attribute, and finish by telling you exactly which files and variables the endpoint and token go in. Expect it to come back with questions, so keep this screen open while it works. Wiring the exporter by hand works too — both fields are right there — but you still need that attribute, since it is how Tessary attributes a span to a call site (a place in your code that invokes a model) at all.The gate does not open on any span landing. It opens on a tagged one:- Nothing has arrived yet: the screen shows a pulsing “listening” indicator and stays as it is.
- Spans arrive but none carry
tessary.call_site.id: the screen replaces itself with a narrower repair prompt — the same guide, deep-linked to its tagging section only, so your agent adds the attribute and leaves the working exporter alone — with a live count of spans received versus spans tagged. There is no other exit from this state — the fix is always to tag the spans that are already arriving. - A tagged span lands: the gate opens on its own and the project drops you on Triage.
sent one span tagged tessary.call_site.id=first-span on HTTP 200, the gate opens on that span, and the ladder below reads fitting on its next poll. The script is plain Node with no dependencies; read it to see exactly what a span needs to carry.Would rather look around first? A quiet Start with a sample project link (visible only in the first, “nothing yet” state) creates a second, clearly-labeled project full of generated data and drops you straight into it — connecting your own traces is still the default and the only way into your real project’s data.Confirm traces are arriving
Once you are past the gate, Tessary tracks the project’s progress through a milestone ladder — a broader, ongoing read than the gate’s own tagged-span check. Triage names the stage the project is in whenever its queue is empty, and says what has to happen for the next one to arrive. The connect fields live under Settings → Sources for later.listening → fitting advances on any span, tagged or not — it is a broader question (“has traffic arrived at all”) than the connect gate’s own (“has a tagged span arrived”), which is why the gate can still be open at fitting if every span so far is untagged. Classifiers need a history of traces to fit a baseline before they can evaluate anything, so a new project stays at fitting for a while before it reaches watching. This is expected, not a failure.Optional: connect a GitHub repository
Connecting a GitHub repository is optional and not required to complete setup. It gives Tessary source code to ground root-cause analysis and triage in, which improves both. Connect one from Settings > Git integration, either through the in-app manifest wizard or with a personal access token.Encoder-based classifiers are unavailable in the open edition
The frustration and groundedness classifiers depend on a separate
classify-service, and they are not available in the open edition. The self-hosting configuration does not start that service, its encoder URL and API key environment variables ship blank, and the model weights the service loads are gated and not published, so standing it up yourself is not a path this documentation can offer. Every other classifier runs out of the box; setup reaches connected traces and a first case without these two.