Sandboxes & Workspaces

A sandbox is an isolated container on your Temps instance that you can run arbitrary commands in. It has its own filesystem, its own network identity, and optionally a copy of one of your git repositories.

Sandboxes are useful for anything you don't want running on your laptop or inside a deployment: running an untrusted PR's test suite, giving an AI agent somewhere to work, reproducing a bug on a clean machine, or handing a contractor an environment without handing over your repo credentials.

Two kinds of sandbox

The difference is what happens after you stop typing.

EphemeralWorkspace
Created withsandbox createsandbox create --workspace
After the idle timeoutContainer stopsContainer stops
Next commandFails with 409Wakes automatically and runs
Files after suspensionKept until you delete itKept until you delete it
Good forCI jobs, one-off commands, SDK-driven automationDevelopment you return to over days

Both classes suspend when idle. That is deliberate: a suspended container costs nothing but disk, whereas a handful of always-on idle containers will happily starve the deployments on a small server. "Persistent" here means your files persist, not that the container runs forever.

The practical difference is the second row. An ephemeral sandbox is owned by whatever created it — a stopped one stays stopped, because a script that shut it down does not want it silently restarting. A workspace is owned by a person, and a person coming back the next morning should just find their work where they left it.

Create an ephemeral sandbox

# Empty container, default image, 1 hour idle timeout
bunx @temps-sdk/cli sandbox create --name test-run

# With code and a longer timeout
bunx @temps-sdk/cli sandbox create \
  --git-url https://github.com/org/repo.git \
  --branch main \
  --timeout 7200

The response includes a sandbox ID like sbx_a1b2c3d4e5f6. Every other command takes that ID.

Create a persistent workspace

From a directory that's already linked to a Temps project, you don't need any flags — the project comes from .temps/config.json, and the repository and branch from your git remote:

cd ~/code/my-repo
bunx @temps-sdk/cli sandbox create --workspace

Or say it explicitly, and start a new branch at the same time:

bunx @temps-sdk/cli sandbox create --workspace \
  --project my-api \
  --branch main \
  --new-branch feat/checkout

Come back whenever. The workspace will have suspended in the meantime, and this wakes it:

bunx @temps-sdk/cli sandbox exec sbx_a1b2c3d4e5f6 -- git status

Choosing the code

sandbox create works out what to clone from the first of these that matches:

  1. An explicit URL--git-url or --tarball-url. No lookups; use this for anything your Temps instance can reach over the network.
  2. --repo owner/name — a repository on one of your connected git providers that has no Temps project. Temps resolves the clone URL and uses the stored provider token, so private repositories work without you handling a credential.
  3. --project <slug>, or the project linked in .temps/config.json, $TEMPS_PROJECT, or your active context. The project's repository, default branch, and git connection are all read from the project.
  4. The git remote of the current directory.
  5. Interactive pickers — connection, then repository, then branch.

Step 5 only runs on an interactive terminal. In CI, a sandbox that can't be resolved from flags fails with a message naming the flag to pass, rather than hanging on a prompt.

Whichever route you take, the create output tells you what was chosen and how it was worked out, so an inferred repository is never a silent guess.

Private repositories

Two ways, and they're mutually exclusive:

  • --git-connection <id> (or letting --repo / --project resolve it) — Temps injects the stored provider token server-side. The token never appears in your shell history, the sandbox's environment, or the clone URL.
  • --git-username + --git-password — passed through GIT_ASKPASS. Use this only for credentials Temps doesn't already hold.

Suspend and wake

Every sandbox has an idle timeout (--timeout, default 1 hour, maximum 24 hours). The timeout is genuinely idle-based: every command you run and every file you read or write pushes the deadline forward. A sandbox in continuous use is never suspended.

When the timeout does elapse, Temps stops the container. Nothing is deleted — the working directory, the home directory, and any volumes all survive.

For a workspace, the next exec or filesystem call starts the container again and then runs your command. You'll notice it as a slower first command, not as an error. Waking is a container start, not a rebuild: nothing is re-cloned and no state is lost.

For an ephemeral sandbox, the same call returns 409 Conflict. Restart it deliberately with sandbox resume, or push the deadline out ahead of time:

bunx @temps-sdk/cli sandbox resume sbx_a1b2c3d4e5f6
bunx @temps-sdk/cli sandbox extend sbx_a1b2c3d4e5f6 --secs 7200

Running commands

# Run and wait. Use `--` before any flags meant for the inner command.
bunx @temps-sdk/cli sandbox exec sbx_a1b2c3d4e5f6 -- npm test

# Long jobs: detach and stream the logs
JOB=$(bunx @temps-sdk/cli sandbox exec sbx_a1b2c3d4e5f6 --detach -- npm run build)
bunx @temps-sdk/cli sandbox logs sbx_a1b2c3d4e5f6 "$JOB"

# Read and write files directly
bunx @temps-sdk/cli sandbox fs read sbx_a1b2c3d4e5f6 --path /workspace/package.json
bunx @temps-sdk/cli sandbox fs write sbx_a1b2c3d4e5f6 --path /workspace/.env --file ./.env

Each exec is a separate command — cd does not carry over between them. Use --cwd to choose a working directory, or sandbox shell below for a session that keeps its state.

Interactive terminal

For anything you'd rather do by hand — including running an AI coding CLI — attach a real terminal:

# A login shell in the sandbox, in your own terminal
bunx @temps-sdk/cli sandbox shell sbx_a1b2c3d4e5f6

# Start (or reattach to) a long-running claude session
bunx @temps-sdk/cli sandbox shell sbx_a1b2c3d4e5f6 --tab claude --cmd claude

Detaching does not kill what's running. The sandbox holds the terminal session itself, so closing your laptop, losing Wi-Fi, or pressing Ctrl-D leaves the program going. Reattach with the same --tab and you land back in the same process, with recent scrollback replayed. That's what makes it practical to leave claude working on something and check back later.

Tabs are named; --tab defaults to main. Use different names to keep, say, an editor and a test-watcher side by side in one sandbox.

Terminals require the Temps PTY agent, which ships in the default sandbox image. A sandbox created from a custom --image won't have it, and sandbox shell will tell you so rather than hanging.

Preview URLs

Anything listening on a port inside the sandbox can be reached from outside:

bunx @temps-sdk/cli sandbox domain sbx_a1b2c3d4e5f6 --port 3000

The hostname embeds an unguessable sandbox label. That is the only thing protecting it by default, so put a password on anything sensitive:

# Prints the password once — it is stored only as a hash
bunx @temps-sdk/cli sandbox password sbx_a1b2c3d4e5f6 --rotate
bunx @temps-sdk/cli sandbox password sbx_a1b2c3d4e5f6 --clear

Resource limits and isolation

bunx @temps-sdk/cli sandbox create \
  --cpu-limit 1.0 \
  --memory-mb 2048 \
  --image node:22

Sandboxes run as Docker containers by default, with capabilities dropped and no-new-privileges set. Hosts provisioned for it can also run sandboxes as Firecracker microVMs for a hardware-virtualised boundary — see Firecracker Sandboxes for host requirements and how to select the backend per sandbox. Asking for a backend the host can't provide fails the request rather than quietly downgrading your isolation.

Cleaning up

Nothing deletes a sandbox for you — not the idle sweep, and not any background job. A suspended workspace keeps its disk until you say otherwise:

bunx @temps-sdk/cli sandbox list --workspace
bunx @temps-sdk/cli sandbox rm sbx_a1b2c3d4e5f6

On a small server, workspaces are the thing most likely to fill your disk quietly. sandbox list is worth checking periodically.

API and SDK

Everything above is the /v1/sandbox REST API, documented in the OpenAPI spec at /api/api-docs/openapi.json under the Sandboxes tag.

The Node SDK is shape-compatible with @vercel/sandbox, so existing code against that API works with an import change:

import { Sandbox } from '@temps-sdk/sandbox'

const sandbox = await Sandbox.create({
  source: { type: 'git', url: 'https://github.com/org/repo.git', revision: 'main' },
  timeoutSecs: 3600,
})

const { stdout } = await sandbox.exec(['npm', 'test'])
const previewUrl = sandbox.domain(3000)
await sandbox.stop()

See the CLI reference for every flag.

Was this page helpful?