Skip to main content
By default boxes run in Tesser’s AWS account and the org is charged per awake second, plus a few cents a day for each sleeping box. An org can instead run its boxes in its own account: Tesser still hosts the control plane, the CLI, and the dashboard, but every EC2 instance is yours, in your VPC, on your AWS bill. Tesser bills only its upcharge per awake hour, from $0.02, and nothing while a box sleeps (the pricing page has the rates). Running in your own account is part of the Team plan. A self-hosted box:
  • has no inbound SSH port. Laptops reach sshd through AWS Session Manager, so the security group admits nothing from outside the org’s own boxes, which reach each other on TCP 14650.
  • never runs its user-data. The user-data is three fields of JSON: the org id, box id, and control plane URL. None of it is secret. The image turns cloud-init’s user-data processing off, so nothing in it is executed. On first boot a step baked into the image reads it from the instance metadata service, refuses to go on unless every field is well formed (an https control plane URL), fetches boxd, and installs it only if its release signature verifies.
  • holds no Tesser credential. boxd proves which instance it runs on with a short-lived token that AWS STS signs for the box’s role (sts:GetWebIdentityToken, outbound identity federation). The control plane accepts it only when the token names this box’s instance in your account, addressed to the control plane.
  • launches only the image your role allows. By default that is Tesser’s published image: your role may launch images owned by Tesser’s publishing account and no one else’s. To own the image outright, copy a release into your account and pin the role to the copy (Own the image).

Set it up

You need to be an admin of the org, and the org needs to have no boxes: a box cannot move between clouds, so tesser rm any it has first. The setup is a Pulumi program you copy into your own infrastructure repository; it needs a @pulumi/aws 7 release with aws.iam.OutboundWebIdentityFederation. It is two files: tesser-policy.ts holds the IAM documents, and index.ts makes the network, the roles, and the instance profile. Two values come from Tesser:
The first is the org id, the second the AWS account that publishes Tesser’s box image. Set them on the stack, in us-east-1 or us-west-2 (the regions Tesser publishes its image to):
tesser-policy.ts
index.ts
When pulumi up finishes, run the command it exports as connect, with the ~/.aws profile members’ laptops use for Session Manager:
--account finds the role (tesser-<org id>), the boxes’ instance profile (tesser-<org id>-box), and the subnet and security group tagged with the org id. The account id can be pasted with dashes, the way the AWS console shows it. From then on every tesser make, dev, and exec launches in your account. Boxes sleep, wake, and are removed exactly as before. The program makes, in the region you run it in:
  • A VPC (10.73.0.0/16) with one public subnet, an internet gateway, and a route to it. The subnet sits in a zone that offers every box size: use1-az1 in us-east-1, usw2-az1 in us-west-2. Boxes need that egress to reach Session Manager and api.tesser.sh.
  • A security group: TCP 14650 from the group itself so boxes reach each other, all outbound, and nothing else inbound. No port 22.
  • The identity provider for https://api.tesser.sh. An account holds one per URL; when a second org sets up in the same account, drop the OpenIdConnectProvider from its program and look up the existing one with aws.iam.OpenIdConnectProvider.get.
  • A role named tesser-<org id> that only this org can assume, with the permissions policy from tesser-policy.ts.
  • Outbound identity federation, switched on for the account, so the boxes’ role can ask STS for identity tokens. It is one account-wide setting; a second org in the same account drops it from its program.
  • The boxes’ role and instance profile, tesser-<org id>-box, with AmazonSSMManagedInstanceCore, sts:GetWebIdentityToken for tokens addressed to https://api.tesser.sh only and, with tesser:parameterStore, read access to the org’s parameters.
None of it costs anything. Boxes cost what the same EC2 instances and EBS volumes cost anyone else. pulumi destroy removes everything it made; disconnect the org first. To use an existing VPC, a private subnet behind a NAT (the CLI then reaches boxes at their private IP with --private), or your own role names, keep tesser-policy.ts and change index.ts, then connect with the ids (--role, --subnet, --sg, --instance-profile) instead of --account. tesser cloud template --account <id> --region <region> prints the same policies with your values filled in. PassBoxRole names the IAM role inside the boxes’ instance profile, not the profile. The program gives the role and the profile the same name, and template assumes that: it uses the org’s instance profile name (or tesser-<org id>-box before the org is connected). If your role’s name differs from the profile’s, or has a path, pass it as boxRole (in the program) or --box-role path/name (to template).

Own the image

By default boxes launch Tesser’s published image, and the role may launch any image Tesser’s publishing account owns. To take that choice away from Tesser, copy an image release into your account and pin the role to the copy. Tesser then launches exactly that image and no other, and a new release reaches your boxes only when you copy it and move the pin. Every image build of github.com/tesser-sh/ami is a release named build-<run id>. Its tag points at the commit that records the build’s AMI ids in amis.json, one per region: { "us-east-1": "ami-...", "us-west-2": "ami-..." }. Pin that commit, never main: the sha is what pins the release, and it is the tree to review. This prints the latest release’s commit:
Replace the image line of index.ts with:
Move amisSha to a newer release’s commit to take a new image. The permissions policy’s LaunchImage statement becomes arn:aws:ec2:<region>::image/<your copy> with no owner condition, and tesser:imageOwner is no longer needed. Copy a release soon after you pin its sha: Tesser keeps only its two newest images public, and an older source can no longer be copied. Then tell the control plane which image to launch:
--image tesser goes back to the published image; move the policy back first. The image can change while boxes run: running and sleeping boxes keep the image they launched from, and new boxes take the new one.

Serve shared boxes on your own domain

A share gateway makes every shared box answer at https://box-<id>.<host>/ for anyone on your network, with no Tesser login: a teammate, a designer, or a phone on the VPN opens the URL. The first port the box’s service declares is served there, and every other port at https://box-<id>--<port>.<host>/, so each port is its own site the way localhost:<port> is. It is one process, tesser gateway serve, on a small instance in the boxes’ VPC, behind an internal Application Load Balancer that terminates TLS with an ACM certificate for *.<host>. Opening a sleeping box’s URL wakes it, with a page that shows the progress and turns into the app when it answers. The gateway proxies HTTP and WebSockets to the box’s mesh port and keeps the box awake while traffic flows. A box is served only while it has a live share: tesser share <box> prints the box’s gateway URL next to the review link, and tesser share <box> --revoke, or the share expiring, takes it off within 15 seconds. The gateway does no user auth. Whoever can reach it reaches every shared box, so admit only your VPN or an SSO proxy to it. It proves itself to Tesser with its IAM role’s AWS web identity token (sts:GetWebIdentityToken, the same outbound identity federation the boxes use) and holds no Tesser secret. Add this to index.ts, with the host the boxes hang off and the CIDR your people reach the VPC from:
ACM issues the certificate once <host>’s public DNS carries a validation record: pulumi up exports it as certificateValidation. Add it to your DNS once and leave it there, and ACM renews the certificate on its own. A <host> with no public DNS can’t get an ACM certificate; import one into ACM instead and pass its ARN with pulumi config set tesser:gatewayCertificate <arn>.
gateway.ts
After pulumi up, run the command it exports as gatewaySet. The load balancer’s group admits HTTPS from gatewayFrom and sends only to the gateway’s port; the gateway’s group admits only the load balancer. The gateway’s egress is HTTPS (Tesser, STS, Session Manager) and the boxes’ mesh port, which the boxes’ group admits from it. The instance carries no tesser:org tag, so Tesser’s role can neither stop nor sweep it. The hosted zone is private to the VPC: forward <host> to it from your VPN’s DNS (a Route 53 Resolver inbound endpoint, or Tailscale split DNS), or point a wildcard record in your own DNS at the load balancer. The dev server sees what it sees on a laptop: the gateway presents each request as http://localhost:<port> (with a prefix in front of the box’s label kept, as <prefix>.localhost), so Vite’s allowedHosts and Next’s allowedDevOrigins need nothing added. A redirect the app builds to http://localhost:<port> goes back to the browser as the gateway URL, and the gateway adds a small script to each page that sends the page’s own fetch, XMLHttpRequest, WebSocket, and EventSource calls to localhost:<port> through the gateway, so a frontend that calls its API at localhost:5000 works unchanged. Links, forms, and image and script sources that point at localhost:<port> are rewritten too, whether they come in the page’s HTML or are added by its scripts (a script-added link or form is fixed when it is clicked or submitted). Each port’s site keeps its own cookies, where localhost shares them across ports, and a third-party sign-in only returns to the gateway URL if the provider allows it as a callback. Only HTTP and WebSockets cross the gateway.

What connect checks

connect tests the account end to end and stores nothing if any check fails. Each failure names what is wrong and how to fix it: tesser cloud check runs them again at any time without changing anything. tesser cloud shows the config, and AWS errors from the last seven days: a call AWS keeps refusing, such as a StopInstances a tightened policy no longer allows, stays listed until it works again. A dry run cannot test stopping or terminating an instance that does not exist yet, so those only surface there.

What the control plane can do

Your code, the disks, the dev server logs, and the traffic between boxes stay in your account. Where your code goes lists every hop and everything the control plane receives. What the control plane can still do to your boxes, through the role and boxd’s WebSocket:
  • Start, stop, and terminate the org’s instances, and launch new ones from the image the role allows, into your subnet and group.
  • Start and stop the dev server declared in your manifest. A start names only the service; boxd reads the command from the manifest on the box’s own disk.
  • Set its env. boxd drops keys that load a program (NODE_OPTIONS, LD_PRELOAD, BASH_ENV, PROMPT_COMMAND, the GIT_ and NPM_CONFIG_ families, and more). The list is a denylist, so it covers the known hooks, not every program’s. With Parameter Store the control plane can write values but not read them.
  • Update boxd. An update makes boxd fetch a new bundle from the control plane, and boxd installs it only if it carries Tesser’s release signature; first boot checks the same signature. The control plane can pick which signed release runs, not write its own.
  • Replace authorized_keys. sshd has no network path except Session Manager, which your IAM gates, so a key alone reaches nothing.

How Tesser reaches your account

Tesser never holds a credential for your account. The control plane is an OpenID Connect issuer, and the role trusts it for exactly one org id. Each time the org cell needs EC2 it signs a token that lives five minutes, trades it for one hour of temporary credentials with AssumeRoleWithWebIdentity, and uses those. Another org’s cell cannot assume your role: the trust policy names the org in sub, and the token carries the org that signed it. The permissions are fenced the same way: the role can only launch instances tagged with the org id, and only stop, start, or terminate instances that carry it.

Session Manager

Every ssh, sync, and daemon tunnel runs tesser ssm-proxy as its ProxyCommand: tesser’s own Session Manager client, so no AWS CLI and no session-manager-plugin on the laptop. The host key is filed under the instance id. What it needs:
  • The instance profile carries AmazonSSMManagedInstanceCore (the SSM agent ships in the Tesser image), and the role tesser assumes has iam:PassRole on its role. The program above does both.
  • Every org member needs an AWS profile in ~/.aws/config (or ~/.aws/credentials) for the account, with ssm:StartSession and ssm:TerminateSession on the boxes. Any profile kind works (SSO, static keys, source_profile + role_arn, credential_process); tesser reads the same files and SSO token cache the AWS CLI does, so aws sso login --profile <name> is still how an SSO profile signs in. --aws-profile <name> on cloud connect is the org’s default profile name, the one every member’s laptop is expected to have; a laptop whose profile is named differently overrides it with tesser cloud aws-profile <name>, kept in that laptop’s tesser config. With --sso-start-url <portal> --sso-region <region> --sso-role <permission set> on cloud connect, members don’t have to set anything up: a laptop missing the profile, with nothing else for the account, gets it appended to ~/.aws/config (account from the role ARN) the first time it makes or wakes a box, and the SSO sign-in below takes it from there. A profile the laptop already has is never touched.
  • Agents and CI don’t need the profile. When it is missing or will not resolve, tesser uses the default AWS credential chain (env keys, an instance or container role, the default profile) once STS says those credentials are for the org’s account; credentials for any other account are never used. When neither works (SSO session expired, keys rejected), every ssh-driven verb fails immediately and the daemon holds its tunnels until it works again. For an SSO profile the verb also opens /__tesser/aws?profile=<name> on the daemon’s port: the daemon starts the device-code sign-in aws sso login --use-device-code does, shows the code, links to the AWS approval page, and writes the token to ~/.aws/sso/cache when it is approved, so no AWS CLI is needed. The tunnels resume on their own. The verb’s error tells an agent to run tesser aws-wait <name> [--timeout 90s], which returns once the code is approved and fails with the reason if it is denied or expires.
That access as an Identity Center permission set, in Pulumi:
ManageOwnSessions is the statement to get right. Scoping it by session ARN the way AWS leads with only matches an IAM user: a federated caller’s ${aws:userid} is AROAEXAMPLEID:you@example.com while the session id is you@example.com-<suffix>, so every terminate is denied, and silently, since tesser ignores a failed close, leaving sessions Active until SSM times them out. The session-tag form above works either way, with the same scoping: your own sessions only. Laptop-to-box transfers (sync, git packs) run at roughly link speed through Session Manager: tesser’s client sends 32 KiB frames with a 4 MiB window, where the official plugin paces itself to ~0.7 MB/s. Box-to-laptop is paced by the SSM agent on the instance at about 0.7 MB/s, which logs and exec output never approach.

Env in your Parameter Store

By default tesser env set stores a service’s values in the org, and the control plane injects them when an instance starts. With --env parameter-store they live in your account instead, one SecureString per key under /tesser/<org id>/<service>/<KEY> in the org’s region, encrypted with the account’s aws/ssm key:
Who touches them:
  • tesser env set / unset go through the control plane, which writes the value into your Parameter Store and keeps only the key’s name, so tesser env ls and the env.required check need no AWS call. The role it assumes needs ssm:PutParameter and ssm:DeleteParameters on parameter/tesser/<org id>/* and nothing else: no ssm:GetParameter*, no ssm:DescribeParameters. That one statement is what you audit. A parameter changed or removed outside tesser env is not noticed by env ls or the env.required check; boxes see it the next time they read.
  • Each box reads the values itself with the instance profile’s credentials: when its dev server starts (a service box reads its service’s, a workbench every service of its repository, as with cell-held env) and, for shells, on connect and after every env set. That profile’s role needs ssm:GetParametersByPath on the same prefix; the Pulumi program adds it with tesser:parameterStore. Any box in the org can read any of the org’s services’ env, which is the same trust boundary as the mesh.
connect proves the role can write and delete under the prefix before storing anything, then moves the values the org already holds into your Parameter Store and forgets them. A value is at most 4 KB (the standard tier), and may not be blank; unset a key to remove it. If a box cannot read its env at start, the run fails with the AWS error in tesser dev’s output rather than starting without it. Moving --env back to tesser, or disconnect, leaves the parameters in your account and the org with no values: set them again. If you later move back to parameter-store, boxes read whatever is still under the prefix, including values the org no longer lists; delete the stale ones or set them again. tesser service rm deletes the service’s parameters. Because these are your parameters, the control plane can also set them, the same way it can start the dev server; what it cannot do is read one.

Changing or leaving

tesser cloud connect --max-boxes <n>, --env <store>, --image <ami>, --aws-profile <name>, and --no-crash-output (boxd keeps a crashed dev server’s last lines on the box instead of sending them with its exit) work at any time; the other flags default to the stored config. Where boxes run cannot change while any exist. Remove them, then connect again with the new ids, or tesser cloud disconnect to return the org to Tesser’s cloud. Disconnecting deletes nothing in your account. pulumi destroy removes the network, the roles, and any image copy when you no longer need them.