Sandbox

Quickstart

Spin up your first Tenki Sandbox session in under a minute. Install the CLI or SDK, authenticate with an API key, and run commands inside an isolated Linux microVM.

Tenki Sandbox provides disposable Linux microVMs that you can control through the CLI, Go SDK, or TypeScript SDK. It's designed for running AI agents, executing untrusted code, automating jobs, spinning up reproducible development environments, and handling one-off compute tasks.

A sandbox session lets you:

  • run shell commands and stream output
  • read and write files
  • expose ports through preview URLs
  • accept SSH connections
  • mount workspace-scoped persistent volumes
  • snapshot and restore VM state
  • spin up new sessions from a reusable template
  • run OpenCode inside the VM for AI-driven workflows

Get started

The steps below cover the full flow: install, authenticate, create a session, run a command, move files, expose a port, and tear down. Pick your tool in any code block — the selection syncs across the rest of the page.

1. Install

Install the latest stable tenki CLI:

curl -fsSL https://tenki.cloud/install.sh | bash

Pin a version when you need reproducibility:

curl -fsSL https://tenki.cloud/install.sh | bash -s -- --version 0.1.0

The binary installs to ~/.local/bin/tenki by default. Override the location with TENKI_INSTALL_DIR.

Verify the install:

tenki --version
tenki sandbox --help

The CLI command group is tenki sandbox, with tenki sbx available as a shorter alias.

npm install @tenki/sandbox
go get github.com/TenkiCloud/tenki-sdk-go/sandbox

2. Authenticate

Generate an API key from your workspace settings, then export it:

export TENKI_API_KEY=tk_your_api_key
# Optional: override the default API endpoint (defaults to https://api.tenki.cloud)
export TENKI_API_URL=https://api.tenki.cloud

The CLI and both SDKs read TENKI_API_KEY automatically. Tokens that start with tk_ are sent as Authorization: Bearer <token>. The SDKs also accept an explicit token through WithAuthToken() (Go) or the authToken option (TypeScript).

3. Create your first session

tenki sandbox create --name demo --cpu 2 --memory-mb 4096

By default the CLI waits for the session to reach READY and prints the session ID. Persist it as the current session so you can omit --session from later commands:

tenki sandbox set <session-id>

The remaining steps assume the current session is set. Pass --session <session-id> explicitly on any command if you skipped set or want to target a different session.

import { TenkiSandbox } from "@tenki/sandbox";

const sandbox = new TenkiSandbox(); // reads TENKI_API_KEY from env

await using session = await sandbox.createAndWait({
  name: "demo",
  cpuCores: 2,
  memoryMb: 4096,
});

await using ensures the session is closed automatically when it leaves scope. The remaining steps assume session is in scope.

package main

import (
  "context"
  "log"
  "time"

  tenkisandbox "github.com/TenkiCloud/tenki-sdk-go/sandbox"
)

func main() {
  ctx := context.Background()

  client, err := tenkisandbox.New() // reads TENKI_API_KEY from env
  if err != nil {
    log.Fatal(err)
  }
  defer client.Close()

  session, err := client.CreateAndWait(
    ctx,
    3*time.Minute,
    tenkisandbox.WithName("demo"),
    tenkisandbox.WithCPUCores(2),
    tenkisandbox.WithMemoryMB(4096),
  )
  if err != nil {
    log.Fatal(err)
  }
  defer session.Close(ctx)

  // Steps 4-6 go here.
}

defer session.Close(ctx) tears the session down when main returns. The remaining steps assume session and ctx are in scope.

4. Run a command

tenki sandbox exec bash -lc 'uname -a && whoami'

You will see streamed stdout and stderr, the final status, exit code, duration, and an execution ID.

const result = await session.exec("bash", {
  args: ["-lc", "uname -a && whoami"],
  timeoutMs: 30_000,
});

console.log(`exit=${result.exitCode} stdout=${result.stdout}`);
result, err := session.Exec(
  ctx,
  "bash",
  tenkisandbox.WithArgs("-lc", "uname -a && whoami"),
  tenkisandbox.WithTimeout(30*time.Second),
)
if err != nil {
  log.Fatal(err)
}

log.Printf("status=%s exit=%d stdout=%s", result.Status, result.ExitCode, result.StdoutString())

5. Move files in and out

echo 'hello sandbox' | tenki sandbox write --path /workspace/hello.txt
tenki sandbox read --path /workspace/hello.txt

You can also write a local file directly:

tenki sandbox write --path /workspace/config.json --data-file ./config.json
await session.writeFile("/workspace/hello.txt", "hello sandbox");
const data = await session.readFile("/workspace/hello.txt");
if err := session.WriteFile(ctx, "/workspace/hello.txt", []byte("hello sandbox")); err != nil {
  log.Fatal(err)
}

data, err := session.ReadFile(ctx, "/workspace/hello.txt")
if err != nil {
  log.Fatal(err)
}
log.Printf("read: %s", data)

6. Expose a port

Start a server inside the sandbox, then publish its port at a public preview URL.

tenki sandbox exec bash -lc 'python3 -m http.server 3000 &'
tenki sandbox expose --port 3000

The CLI prints a preview_url you can open in a browser. List exposed ports with tenki sandbox ports and remove one with tenki sandbox unexpose.

await session.exec("bash", { args: ["-lc", "python3 -m http.server 3000 &"] });

const port = await session.exposePort(3000, { ttlMs: 3600_000 });
console.log(port.previewUrl);
if _, err := session.Exec(ctx, "bash", tenkisandbox.WithArgs("-lc", "python3 -m http.server 3000 &")); err != nil {
  log.Fatal(err)
}

port, err := session.ExposePort(ctx, 3000)
if err != nil {
  log.Fatal(err)
}
log.Printf("preview URL: %s", port.PreviewURL)

7. Tear it down

tenki sandbox terminate

The await using declaration in step 3 closes the session automatically when it leaves scope. To tear down explicitly:

await session.terminate();

The defer session.Close(ctx) in step 3 tears the session down when main returns. To terminate explicitly:

if err := session.Terminate(ctx); err != nil {
  log.Fatal(err)
}

What's next

  • Read the Concepts page to learn how sessions, volumes, snapshots, and templates fit together.
  • See Sessions for command execution, file I/O, port exposure, and SSH.
  • See Volumes, Snapshots, and Templates for durable state.
  • The full SDK reference covers every option for both Go and TypeScript.

Need help? Email us at [email protected] and we'll be happy to onboard you.

LinkedInProduct Hunt