Documentación
Sandboxes
Per-project cloud lanes and the per-task forks that run on them.
En esta página
One lane per project, one fork per task
A TDE Sandbox is a cloud environment where a task's code actually runs. The shape is deliberate and worth learning before anything else:
- You provision a Project Base once per project — a single isolated cloud instance that installs your dependencies and takes a checkpoint.
- Each task activates on that same instance: the base checkpoint is restored, the current source is uploaded, task setup runs, services start.
So a sandbox is not a fresh disposable VM per task. It is one reusable lane per project, restored to a known-good point for each task. That is why activation takes seconds instead of a full dependency install, and it is also why only one task can occupy the lane at a time.
Sandboxes are an add-on and require a plan with sync — see /pricing.
The Sandbox Profile
.tde/sandbox.toml, committed at your repository root, describes how to build and
run the project.
version = 1
runtime = "docker-compose"
toolchain = "node@22"
compose_files = ["docker-compose.yml"]
[[setup]]
id = "deps"
command = "npm ci"
cache_key = "package-lock.json"
checkpoint_after = true
[[task_setup]]
id = "migrate"
command = "npm run db:migrate"
[[preview]]
name = "web"
route = ""
health_path = "/healthz"
[services]
required = ["db"]
[secrets]
allow = ["DATABASE_URL", "STRIPE_*"]| Key | Type | Meaning |
|---|---|---|
version | integer | Required. Must be 1. |
runtime | auto · docker-compose · native | Required. auto picks Compose when compose_files is non-empty. |
toolchain | string | Free-form toolchain hint. |
compose_files | string list | Your Compose files, relative to the repository. |
compose_project | string | Compose project name. Supports the sandbox id placeholder. Defaults to a per-sandbox name. |
start | string | Shell command for a non-Compose project. |
primary_preview | string | Names one of the previews below. |
retention | duration string | Defaults to one day. |
[[setup]] | table list | Steps that build the Project Base. |
[[task_setup]] | table list | Steps that run on every task activation. |
[[preview]] | table list | Named HTTP entry points. |
[services] | table | required, optional and disabled service names. |
[secrets] | table | allow — the environment-variable allowlist. |
A setup step takes id (unique across both phases), command, and optionally
cache_key, health, sensitive = true (its output is redacted in the log) and
checkpoint_after = true (snapshot the instance after this step succeeds).
A preview takes name, route, and optionally port_env and health_path.
Only three things are validated: the version is 1, step ids are unique, and
primary_preview names a declared preview. Unknown keys are ignored silently.
Provisioning the Project Base
Project → Configure cloud sandbox opens a report showing the detected stack, runtime, toolchain, package manager, any Compose warnings, the HTTP ports it found, and the resolved profile. From there you provision.
Provisioning runs as a resumable plan: an ordered list of steps, each with its own state and an input hash. After every step the session is persisted to disk, and resuming re-walks the plan, skipping any step whose inputs have not changed. A desktop restart mid-provision does not lose the work — but TDE will not silently resume either; it offers an explicit Resume action.
| State | Meaning |
|---|---|
requested · creating | Allocating the instance. |
restoring_cache · starting_services · health_checking | Building. |
ready | Usable as a fork source. |
sleeping | Idle; wakes on demand. |
failed | See the error; a failed base starts fresh rather than resuming. |
The base is only usable as a fork source once it is ready or sleeping and
has a checkpoint. That checkpoint is taken after a setup step marked
checkpoint_after, or as a fallback at the end of provisioning.
Activating a task
Once the base is ready, a task's Sandbox tab offers Activate. Activation
restores the base checkpoint, uploads the current source, runs task_setup and
starts services.
| State | Progress row |
|---|---|
restoring_base | Restore base |
syncing_source | Sync source |
setting_up | Task setup |
starting_services | Start services |
ready | Ready |
Source is uploaded as an archive of Git-visible files only — symlinks and excluded paths are skipped — and lands as a clean checkout with a snapshot commit. No Git credential, deploy key or token is ever sent to the sandbox.
Service startup depends on the runtime. With Compose, TDE validates the composition,
brings it up under a per-sandbox project name, and then requires every service in
[services] required plus every non-disabled preview to actually be running. With a
native start command, the process is backgrounded and its liveness checked; if it
died, the tail of its log is reported. If the profile declares neither, activation
reaches ready with nothing running — a silent no-op that is easy to mistake for
success.
Failures roll back: Compose is brought down, and the session records both the error message and the state it failed in.
Reaching what is running
In practice you get two ways in, both from the task's Sandbox tab:
- Open on an HTTP preview opens the composed URL in the TDE Browser.
- Open on a Compose-only service — a database, a queue, a cache — opens a terminal window attached inside the container. That is how you reach anything that is not HTTP.
If the profile declares no services at all, TDE synthesizes a single Application
card pointing at the instance root.
Secrets
Secrets come from your project's dotenv files, resolved the same way TDE resolves a
Project Environment, and they pass two allowlist checks: once
when the grant is built and again inside the sandbox layer, which hard-errors on any
name outside [secrets] allow. Rules are exact names or a single trailing *; a
bare * is rejected.
Grants are denied by default. They are only honoured when the persisted base
profile matches the current one, and wildcards are expanded to concrete names before
being stored — which is what makes a new variable in .env require an explicit
re-provision rather than silently widening the grant.
In transit and at rest inside the sandbox, values are handled deliberately:
- Uploaded through a private channel that is separate from the source archive, so they can never be enumerated through the repository tree.
- Written owner-only into a directory scoped to that one operation, sourced per command, and removed before the finalize phase.
- Redacted out of every provider error and log line before it reaches a session file, longest value first.
- Never serialized: the in-memory grant is zeroized on drop and prints as
[REDACTED].
Compose specifics
TDE reads your Compose files and reports what it finds before you provision:
unenforced resource limits, privileged: true, host mounts of the Docker socket or
of /proc, /sys and cgroups, UDP-only ports, and aggressive PostgreSQL memory
settings. TCP ports become preview candidates.
At activation it generates a non-destructive override, .tde/compose.sandbox.yaml,
and layers it on top of your files. Your own Compose file is never rewritten. Do not
list the generated override in compose_files — it is filtered out anyway. TDE also
creates placeholder env_file targets so Compose does not hard-fail on a file that
only exists on your laptop.
Docker itself must already be present in the sandbox image. TDE does not install a Docker engine, register a daemon service or manage image and volume usage for you.
Operating and troubleshooting
One operation at a time, globally. A single background worker serves every project. Starting a second provision or activation while one is running is refused with "Another sandbox operation is already running". A task trying to take a lane that another task holds is told which task holds it.
Ownership is fenced. Every provider call and every durable write re-checks a lease — held locally, optionally across your devices, and mirrored as a fence file on the instance itself. Losing it suppresses the terminal write entirely, so a successor is never clobbered by a stale worker.
Where state lives. Sessions are plain JSON under ~/.tde/sandboxes/: one file
per Project Base, one per task activation, plus a lane-owner record. When the task
tab shows a truncated error, the full message and the failed_state field are in the
task activation file.
Usage reports
Open Account → Reports → Sandboxes for a monthly statement of how your account used Sandboxes. The runtime reports durable usage events; the report separates the measurements instead of collapsing them into one vague number:
| Measurement | What it means |
|---|---|
| Base session | Time a Project Base was up, proven by runtime session heartbeats. Silence closes the interval, so a crashed runtime never accrues time. |
| My Loop active | Union of active task intervals per sandbox, broken down by task without double-counting overlapping work. |
| Provisions / Checkpoints / Restores | How many times a Base was provisioned, checkpointed, and restored during the month. |
The current month updates as runtime evidence arrives. Previous-month navigation appears only when your account actually has usage in an earlier month; months without data never appear. Each statement includes per-sandbox, per-task and per-day breakdowns. Usage evidence is retained for 400 days.
Not implemented yet
These appear in design material and are worth ruling out explicitly:
- Branded per-task or per-service preview subdomains, HTTP path routing and port forwarding.
- Delete, reset, restore-checkpoint and restart-services controls. Checkpoints are captured and restored automatically; there is no manual restore.
- Retention and expiry enforcement for the sandboxes themselves, checkpoint pruning, storage accounting.
- Plan quotas and cost estimates. Usage is measured and reported (see above), but nothing is metered for billing.
- Managed secret sets, egress policy and multi-lane concurrency.
- A
tde sandboxCLI. Sandboxes are driven from the desktop only.