Skip to main content
E2B sandboxes provide multiple ways to run commands and interact with the terminal: the PTY module for interactive terminal sessions, and SSH access for remote connectivity.

Interactive terminal (PTY)

The PTY (pseudo-terminal) module allows you to create interactive terminal sessions in the sandbox with real-time, bidirectional communication. Unlike commands.run() which executes a command and returns output after completion, PTY provides:
  • Real-time streaming - Output is streamed as it happens via callbacks
  • Bidirectional input - Send input while the terminal is running
  • Interactive shell - Full terminal support with ANSI colors and escape sequences
  • Session persistence - Disconnect and reconnect to running sessions

Create a PTY session

Use sandbox.pty.create() to start an interactive bash shell.
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
  cols: 80,              // Terminal width in characters
  rows: 24,              // Terminal height in characters
  onData: (data) => {
    // Called whenever terminal outputs data
    process.stdout.write(data)
  },
  envs: { MY_VAR: 'hello' },  // Optional environment variables
  cwd: '/home/user',          // Optional working directory
  user: 'root',               // Optional user to run as
})

// terminal.pid contains the process ID
console.log('Terminal PID:', terminal.pid)
The PTY runs an interactive bash shell with TERM=xterm-256color, which supports ANSI colors and escape sequences.

Timeout

PTY sessions have a configurable timeout that controls the session duration. The default is 60 seconds. For interactive or long-running sessions, set timeoutMs: 0 (JavaScript) or timeout=0 (Python) to keep the session open indefinitely.
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
  cols: 80,
  rows: 24,
  onData: (data) => process.stdout.write(data),
  timeoutMs: 0,  // Keep the session open indefinitely
})

Send input to PTY

Use sendInput() in JavaScript or send_stdin() in Python to send data to the terminal. These methods return a Promise (JavaScript) or complete synchronously (Python) - the actual output will be delivered to your onData callback.
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
  cols: 80,
  rows: 24,
  onData: (data) => process.stdout.write(data),
})

// Send a command (don't forget the newline!)
await sandbox.pty.sendInput(
  terminal.pid,
  new TextEncoder().encode('echo "Hello from PTY"\n')
)

Resize the terminal

When the user’s terminal window changes size, notify the PTY with resize(). The cols and rows parameters are measured in characters, not pixels.
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
  cols: 80,
  rows: 24,
  onData: (data) => process.stdout.write(data),
})

// Resize to new dimensions (in characters)
await sandbox.pty.resize(terminal.pid, {
  cols: 120,
  rows: 40,
})

Disconnect and reconnect

You can disconnect from a PTY session while keeping it running, then reconnect later with a new data handler. This is useful for resuming terminal sessions after network interruptions, sharing terminal access between multiple clients, or implementing terminal session persistence.
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

// Create a PTY session
const terminal = await sandbox.pty.create({
  cols: 80,
  rows: 24,
  onData: (data) => console.log('Handler 1:', new TextDecoder().decode(data)),
})

const pid = terminal.pid

// Send a command
await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo hello\n'))

// Disconnect - PTY keeps running in the background
await terminal.disconnect()

// Later: reconnect with a new data handler
const reconnected = await sandbox.pty.connect(pid, {
  onData: (data) => console.log('Handler 2:', new TextDecoder().decode(data)),
})

// Continue using the session
await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo world\n'))

// Wait for the terminal to exit
await reconnected.wait()

Kill the PTY

Terminate the PTY session with kill().
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
  cols: 80,
  rows: 24,
  onData: (data) => process.stdout.write(data),
})

// Kill the PTY
const killed = await sandbox.pty.kill(terminal.pid)
console.log('Killed:', killed)  // true if successful

// Or use the handle method
// await terminal.kill()

Wait for PTY to exit

Use wait() to wait for the terminal session to end (e.g., when the user types exit).
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
  cols: 80,
  rows: 24,
  onData: (data) => process.stdout.write(data),
})

// Send exit command
await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n'))

// Wait for the terminal to exit
const result = await terminal.wait()
console.log('Exit code:', result.exitCode)

Interactive terminal (SSH-like)

Building a fully interactive terminal (like SSH) requires handling raw mode, stdin forwarding, and terminal resize events. For a production implementation, see the E2B CLI source code which uses the same sandbox.pty API documented above.

SSH access

SSH access enables remote terminal sessions, SCP/SFTP file transfers, and integration with tools that expect SSH connectivity.

Quickstart

1

Build the SSH template

Define a template with OpenSSH server and websocat:
// template.ts
import { Template, waitForPort } from 'e2b'

export const template = Template()
  .fromUbuntuImage('25.04')
  .aptInstall(['openssh-server'])
  .runCmd([
    'curl -fsSL -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl',
    'chmod a+x /usr/local/bin/websocat',
  ], { user: 'root' })
  .setStartCmd('sudo websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22', waitForPort(8081))
Build the template:
// build.ts
import { Template, defaultBuildLogger } from 'e2b'
import { template as sshTemplate } from './template'

await Template.build(sshTemplate, 'ssh-ready', {
  cpuCount: 2,
  memoryMB: 2048,
  onBuildLogs: defaultBuildLogger(),
})
2

Create a sandbox from the template

import { Sandbox } from 'e2b'

const sbx = await Sandbox.create('ssh-ready')
console.log(sbx.sandboxId)
3

Connect from your machine

# Install websocat
brew install websocat

# Connect to your sandbox
ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' user@<sandbox-id>

How it works

This method uses websocat to proxy SSH connections over WebSocket through the sandbox’s exposed ports.
┌───────────────────────────────────────────────────────────┐
│  Your Machine                                             │
│  ┌──────────┐    ProxyCommand    ┌──────────────────┐     │
│  │   SSH    │ ────────────────── │    websocat      │     │
│  │  Client  │                    │   (WebSocket)    │     │
│  └──────────┘                    └─────────┬────────┘     │
└────────────────────────────────────────────┼──────────────┘

                         wss://8081-<sandbox-id>.e2b.app

┌────────────────────────────────────────────┼──────────────┐
│  E2B Sandbox                               ▼              │
│                                  ┌──────────────────┐     │
│                                  │    websocat      │     │
│                                  │  (WS → TCP:22)   │     │
│                                  └─────────┬────────┘     │
│                                            │              │
│                                  ┌─────────▼────────┐     │
│                                  │   SSH Server     │     │
│                                  │   (OpenSSH)      │     │
│                                  └──────────────────┘     │
└───────────────────────────────────────────────────────────┘