Sandbox

Sessions

Create and drive Tenki Sandbox sessions covering lifecycle, command execution, file I/O, port exposure, and SSH access.

A session is one running sandbox VM. This page covers the full session-level surface: lifecycle, command execution, file I/O, ports, and SSH.

Create a session

CLI

tenki sandbox create \
  --name my-session \
  --cpu 4 \
  --memory-mb 8192 \
  --allow-inbound \
  --allow-outbound \
  --env APP_ENV=dev \
  --metadata owner=alice \
  --metadata purpose=review

Supported flags on tenki sandbox create:

  • --name
  • --cpu, --memory-mb
  • --allow-inbound, --allow-outbound
  • --max-duration
  • --snapshot, --template (mutually exclusive)
  • --metadata key=value (repeatable)
  • --env key=value (repeatable)
  • --authorized-key, --authorized-keys-file (repeatable)
  • --volume <volume-id>:<mount-path>[:ro] (repeatable)
  • --no-wait, --wait-timeout

By default, the CLI waits for the session to become READY before returning. Pass --no-wait to return immediately.

Go SDK

session, err := client.Create(
  ctx,
  tenkisandbox.WithName("demo"),
  tenkisandbox.WithCPUCores(4),
  tenkisandbox.WithMemoryMB(8192),
  tenkisandbox.WithAllowInbound(true),
  tenkisandbox.WithAllowOutbound(true),
  tenkisandbox.WithEnvs(map[string]string{"APP_ENV": "dev"}),
  tenkisandbox.WithMetadata(map[string]string{"owner": "alice"}),
)

// Or create-and-wait in one call:
session, err := client.CreateAndWait(ctx, 3*time.Minute, tenkisandbox.WithName("demo"))

Useful create options:

  • WithName, WithCPUCores, WithMemoryMB, WithMaxDuration
  • WithAllowInbound, WithAllowOutbound
  • WithMetadata, WithEnvs
  • WithSSHKeys
  • WithVolume(volumeID, mountPath, ...VolumeOption)
  • WithSnapshot, WithTemplate
  • WithOpenCode, WithOpenCodeProvider
  • WithCloneRepo, WithGitHubToken

Defaults applied by Create: inbound=false, outbound=true, cpu=2, memory=4096 MB.

TypeScript SDK

const session = await sandbox.createAndWait({
  name: "demo",
  cpuCores: 4,
  memoryMb: 8192,
  allowInbound: true,
  allowOutbound: true,
  env: { APP_ENV: "dev" },
  metadata: { owner: "alice" },
});

Lifecycle

err = session.WaitReady(ctx, 3*time.Minute)
err = session.Refresh(ctx)
err = session.Extend(ctx, 30*time.Minute)
err = session.Close(ctx)
err = session.CloseIfOpen(ctx)

In TypeScript:

await session.extend(600_000); // +10 minutes
await session.pause();
await session.resume();
await session.close(); // or use Symbol.asyncDispose

List and inspect

tenki sandbox list
tenki sandbox list --json
tenki sandbox get --session <session-id>
sessions, err := client.List(ctx)
session, err := client.Get(ctx, sessionID)

Template inheritance

If you create a session from a template:

  • the template's CPU and memory defaults are inherited unless you override them
  • explicit --cpu / --memory-mb always win

Command execution

CLI

tenki sandbox exec --session <session-id> bash -lc 'go test ./...'
tenki sandbox exec --session <session-id> --timeout 2m bash -lc 'npm ci && npm test'

CLI output includes:

  • streamed stdout and stderr
  • final status, exit code, and duration
  • the execution ID

Go SDK

result, err := session.Exec(
  ctx,
  "bash",
  tenkisandbox.WithArgs("-lc", "echo $APP_ENV && make test"),
  tenkisandbox.WithEnv("APP_ENV", "ci"),
  tenkisandbox.WithTimeout(2*time.Minute),
)
if err != nil {
  log.Fatal(err)
}

if !result.Status.IsSuccess() {
  log.Fatalf("failed: exit=%d stderr=%s", result.ExitCode, result.StderrString())
}

Exec options: WithArgs, WithTimeout, WithEnv, WithEnvs.

Result helpers: result.StdoutString(), result.StderrString(), result.Status.IsSuccess(), IsFailed(), IsTimedOut().

TypeScript SDK

// One-shot
const result = await session.exec("npm", {
  args: ["test"],
  timeoutMs: 60_000,
  onOutput: (chunk) => process.stdout.write(chunk.data),
});

// Or stream explicitly
const stream = await session.stream("npm", { args: ["test"] });
for (;;) {
  const chunk = await stream.next();
  if (!chunk) break;
  process.stdout.write(chunk.data);
}
await stream.wait();

File operations

CLI

# inline
tenki sandbox write --session <session-id> --path /workspace/app.env --data 'PORT=3000'

# from a local file
tenki sandbox write --session <session-id> --path /workspace/config.json --data-file ./config.json

# from stdin
cat ./local-file.txt | tenki sandbox write --session <session-id> --path /workspace/input.txt

# read to stdout
tenki sandbox read --session <session-id> --path /workspace/config.json

# read into a local file
tenki sandbox read --session <session-id> --path /workspace/build.log --out ./build.log

SDK

err = session.WriteFile(ctx, "/workspace/hello.txt", []byte("hello"))
data, err := session.ReadFile(ctx, "/workspace/hello.txt")
await session.writeFile("/tmp/config.json", '{"key": "value"}');
const data = await session.readFile("/tmp/config.json");

Port exposure and networking

Each session has independent inbound and outbound settings:

  • allow_outbound=true lets the guest make outbound network calls
  • allow_inbound=true enables inbound exposure workflows

CLI

tenki sandbox expose --session <session-id> --port 3000
tenki sandbox ports --session <session-id>
tenki sandbox unexpose --session <session-id> --port 3000

SDK

port, err := session.ExposePort(ctx, 3000)
ports, err := session.ListExposedPorts(ctx)
err = session.UnexposePort(ctx, 3000)
const port = await session.exposePort(3000, { ttlMs: 3600_000 });
console.log(port.previewUrl);

Don't hard-code preview hostnames

The service returns the preview URL on every expose call. Use the returned value rather than building hostnames yourself. Host patterns are not part of the public contract.

Port numbers must be between 1 and 65535.

SSH access

Direct CLI SSH

tenki sandbox ssh --session <session-id>

Useful flags: --user (default root), --identity-file, --batch-mode, --connect-timeout, --strict-host-key-checking.

You can pass standard SSH arguments after the session ID:

tenki sandbox ssh <session-id> -L 8080:127.0.0.1:8080

Managed SSH config

Install a managed entry into your local SSH config so you can use friendly aliases:

tenki sandbox ssh config install
tenki sandbox ssh config status
tenki sandbox ssh config uninstall

After install:

ssh sbx-<session-uuid>

Authorized keys

Add keys at create time:

tenki sandbox create \
  --authorized-key 'ssh-ed25519 AAAA...' \
  --authorized-keys-file ~/.ssh/authorized_keys

Replace the set later:

tenki sandbox ssh-keys set --session <session-id> --keys-file ~/.ssh/authorized_keys
session, err := client.Create(
  ctx,
  tenkisandbox.WithSSHKeys([]string{"ssh-ed25519 AAAA..."}),
)

err = session.UpdateSSHAuthorizedKeys(ctx, []string{"ssh-ed25519 AAAA..."})

Raw SSH transport

The Go SDK exposes the underlying tunnel for integration with your own SSH tooling:

conn, err := session.SSH(ctx)
defer conn.Close()
const conn = await session.ssh();
conn.write("ls -la\n");
conn.onData((data) => process.stdout.write(data));
conn.close();

Session metadata

Use --metadata key=value (CLI) or WithMetadata (SDK) to tag sessions with arbitrary string pairs, useful for filtering in dashboards, billing attribution, or tying a session to an upstream job ID.

tenki sandbox create --metadata owner=alice --metadata job=ci-1234

Metadata is opaque to the service; it is for your bookkeeping.

LinkedInProduct Hunt