跳转至

更多使用方法

本文整理常见的函数计算云沙箱使用方式,覆盖沙箱生命周期、命令执行、代码解释器、文件系统、Git、动态端口、浏览器自动化、模板查询、VPC、OSS、NAS 等场景。每个场景同时提供 Python 和 TypeScript 示例。

Note

VPC、OSS、NAS 示例需要提前准备对应的阿里云资源,示例中的资源 ID、Bucket、Role ARN 和挂载地址请替换为自己的真实配置。

一、准备环境

安装依赖:

python3 -m venv .venv
source .venv/bin/activate
pip install e2b e2b-code-interpreter python-dotenv requests

安装依赖:

npm init -y
npm install e2b @e2b/code-interpreter dotenv
npm install -D tsx typescript @types/node

创建 .env

E2B_API_KEY=e2b_xxx
E2B_API_URL=https://api.cn-beijing.e2b.fc.aliyuncs.com
E2B_DOMAIN=cn-beijing.e2b.fc.aliyuncs.com

E2B SDK 会自动读取 E2B_API_URLE2B_DOMAIN。为了让示例更明确,下文仍统一从环境变量或配置中读取。TypeScript SDK 使用 import 'dotenv/config' 自动加载 .env 文件。

二、通用辅助函数

后续示例可以复用下面的配置读取逻辑:

import os

from dotenv import load_dotenv


load_dotenv()


def require_env(name: str) -> str:
    value = os.environ.get(name, "").strip()
    if not value:
        raise RuntimeError(f"缺少环境变量: {name}")
    return value


E2B_API_KEY = require_env("E2B_API_KEY")
E2B_CONN_OPTS = {
    "api_url": require_env("E2B_API_URL"),
    "domain": require_env("E2B_DOMAIN"),
}
import 'dotenv/config';

const E2B_API_KEY = process.env.E2B_API_KEY!;
// TypeScript SDK 自动读取 E2B_DOMAIN 环境变量,无需显式传入

TypeScript SDK 在导入 dotenv/config 后,会自动从 .env 加载环境变量。SDK 会自动读取 E2B_DOMAIN,大多数场景无需额外传入连接参数。

生产代码中建议把 sandbox.kill() 放在 finally(Python)或 try/finally(TypeScript)中,确保异常时也能释放沙箱。

三、基础沙箱与命令执行

基础沙箱适合执行 Shell 命令、脚本、轻量调试任务。

from e2b import Sandbox


sandbox = None
try:
    sandbox = Sandbox.create(
        template="base",
        api_key=E2B_API_KEY,
        timeout=300,
        **E2B_CONN_OPTS,
    )
    print(f"sandbox_id: {sandbox.sandbox_id}")

    result = sandbox.commands.run("echo 'Hello, World!' && date")
    print(result.stdout)

    host = sandbox.get_host(3000)
    print(f"port 3000 host: https://{host}")
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
  });

  try {
    console.log(`sandboxId: ${sbx.sandboxId}`);

    const result = await sbx.commands.run("echo 'Hello, World!' && date");
    console.log(result.stdout);

    const host = sbx.getHost(3000);
    console.log(`port 3000 host: https://${host}`);
  } finally {
    await sbx.kill();
  }
}

main();

常用参数:

参数 Python TypeScript 说明
模板 template="base" template: 'base'(创建选项内) 沙箱模板名或模板 ID。未指定时使用默认模板。
存活时间 timeout=300(秒) timeoutMs: 300_000(毫秒) 不传时默认 300 秒。
API Key api_key=... apiKey: ... 云沙箱 API Key。
连接参数 api_url / domain SDK 自动读取 E2B_DOMAIN 北京地域服务入口。

四、timeout 和续期行为

当前云沙箱的 timeout 语义已经与 E2B 对齐:

  • Template 不提供 timeout 相关参数。模板用于定义镜像、CPU、内存、磁盘和预置环境。
  • Sandbox.create(timeout=60, ...) 表示创建出的沙箱初始存活时间为 60 秒。
  • 如果创建沙箱时不传 timeout,默认存活时间为 300 秒。
  • sandbox.set_timeout(...) / sandbox.setTimeout(...) 用于续期,会把沙箱剩余存活时间重置为传入值。
  • 续期不是累加。例如沙箱还剩 120 秒时调用 sandbox.set_timeout(600),新的剩余存活时间是 600 秒,不是 720 秒。

如果任务可能运行超过默认时间,可以在创建时显式传入更大的 timeout,也可以在任务执行过程中按需续期:

from e2b import Sandbox


sandbox = None
try:
    sandbox = Sandbox.create(
        template="base",
        api_key=E2B_API_KEY,
        timeout=1800,
        **E2B_CONN_OPTS,
    )

    # Python SDK 使用秒;这里把剩余存活时间重置为 1800 秒。
    sandbox.set_timeout(1800)

    result = sandbox.commands.run("python3 long_running_task.py", timeout=1700)
    print(result.stdout)
finally:
    if sandbox is not None:
        sandbox.kill()

TypeScript 示例:

import { Sandbox } from 'e2b'

const sandbox = await Sandbox.create({
  template: 'base',
  timeoutMs: 1_800_000,
})

try {
  // TypeScript SDK 使用毫秒;这里把剩余存活时间重置为 1800 秒。
  await sandbox.setTimeout(1_800_000)
  const result = await sandbox.commands.run('python3 long_running_task.py', {
    timeoutMs: 1_700_000,
  })
  console.log(result.stdout)
} finally {
  await sandbox.kill()
}

Warning

timeout / set_timeout / setTimeout 控制的是沙箱生命周期,不是业务重试机制。长任务仍应为命令执行设置合理超时,并确保最终释放沙箱。

五、代码解释器

代码解释器适合运行 Python 代码片段,并在同一个沙箱上下文中保留变量状态。

from e2b_code_interpreter import Sandbox


sandbox = None
try:
    sandbox = Sandbox.create(
        template="code-interpreter-v1",
        api_key=E2B_API_KEY,
        timeout=300,
        **E2B_CONN_OPTS,
    )

    execution = sandbox.run_code("x = 1 + 2\nx")
    print(execution.results[0].text)

    execution = sandbox.run_code("print(f'x is {x}')")
    print(execution.logs.stdout)
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from '@e2b/code-interpreter';

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
  });

  try {
    const execution = await sbx.runCode('x = 1 + 2; x');
    console.log(execution.results[0].text);

    const execution2 = await sbx.runCode("print(f'x is {x}')");
    console.log(execution2.logs.stdout);
  } finally {
    await sbx.kill();
  }
}

main();

适合场景:

  • AI Agent 生成 Python 代码后执行。
  • 数据处理、计算、绘图前置计算。
  • 需要多轮执行并复用变量状态的交互式任务。

六、文件系统操作

沙箱提供文件读写、列目录、创建目录、移动和删除等能力。

import json

from e2b import Sandbox


sandbox = None
try:
    sandbox = Sandbox.create(
        api_key=E2B_API_KEY,
        timeout=300,
        **E2B_CONN_OPTS,
    )

    sandbox.files.write("/tmp/hello.txt", "Hello, E2B!")
    print(sandbox.files.read("/tmp/hello.txt"))

    sandbox.files.write(
        "/tmp/metadata.json",
        json.dumps({"sandbox_id": sandbox.sandbox_id}, indent=2),
    )
    metadata = json.loads(sandbox.files.read("/tmp/metadata.json"))
    print(metadata["sandbox_id"])

    sandbox.files.make_dir("/tmp/work/subdir")
    sandbox.files.write("/tmp/work/subdir/file.txt", "nested content")
    print([item.name for item in sandbox.files.list("/tmp/work/subdir")])

    sandbox.files.rename("/tmp/hello.txt", "/tmp/hello-renamed.txt")
    sandbox.files.remove("/tmp/hello-renamed.txt")
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
  });

  try {
    // 写入和读取文本文件
    await sbx.files.write('/tmp/hello.txt', 'Hello, E2B!');
    console.log(await sbx.files.read('/tmp/hello.txt'));

    // 写入和读取 JSON 文件
    await sbx.files.write('/tmp/metadata.json', JSON.stringify(
      { sandboxId: sbx.sandboxId }, null, 2));
    const metadata = JSON.parse(await sbx.files.read('/tmp/metadata.json'));
    console.log(metadata.sandboxId);

    // 创建目录和嵌套文件
    await sbx.files.makeDir('/tmp/work/subdir');
    await sbx.files.write('/tmp/work/subdir/file.txt', 'nested content');
    const files = await sbx.files.list('/tmp/work/subdir');
    console.log(files.map(f => f.name));

    // 重命名和删除
    await sbx.files.rename('/tmp/hello.txt', '/tmp/hello-renamed.txt');
    await sbx.files.remove('/tmp/hello-renamed.txt');
  } finally {
    await sbx.kill();
  }
}

main();

建议:

  • 临时文件优先放在 /tmp
  • 需要跨沙箱持久化的数据,应使用 OSS、NAS 等外部存储。
  • 大文件传输建议结合上传/下载 URL 或对象存储,避免把大文件塞进控制面请求。

七、传入自定义环境变量

创建沙箱时可以通过 envs 注入业务环境变量。

from e2b import Sandbox


sandbox = None
try:
    sandbox = Sandbox.create(
        template="base",
        api_key=E2B_API_KEY,
        timeout=300,
        envs={
            "APP_MODE": "sandbox",
            "TASK_ID": "task-001",
        },
        **E2B_CONN_OPTS,
    )

    result = sandbox.commands.run("echo $APP_MODE && echo $TASK_ID")
    print(result.stdout)
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

async function main() {
  const sbx = await Sandbox.create({
    template: 'base',
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
    envs: {
      APP_MODE: 'sandbox',
      TASK_ID: 'task-001',
    },
  });

  try {
    const result = await sbx.commands.run('echo $APP_MODE && echo $TASK_ID');
    console.log(result.stdout);
  } finally {
    await sbx.kill();
  }
}

main();

不要通过 envs 传递长期密钥。生产环境建议使用短期凭证、RAM Role 或专门的密钥注入机制。

八、Git 操作

沙箱可以克隆仓库、查看状态、提交变更、创建分支。适合代码分析、自动修复、构建验证等场景。

from e2b import Sandbox


sandbox = None
try:
    sandbox = Sandbox.create(
        api_key=E2B_API_KEY,
        timeout=300,
        **E2B_CONN_OPTS,
    )

    repo_url = "https://gitee.com/aliyunfc/Hello-World.git"
    workdir = "/tmp/git-repo"

    sandbox.git.clone(repo_url, workdir, depth=1)
    status = sandbox.git.status(workdir)
    print(status.current_branch, status.is_clean)

    sandbox.git.configure_user("Sandbox Bot", "sandbox@example.com", path=workdir)
    sandbox.files.write(f"{workdir}/sandbox-note.txt", "created in sandbox\n")
    sandbox.git.add(workdir)
    sandbox.git.commit(workdir, "Add sandbox note")

    sandbox.git.create_branch(workdir, "feature/sandbox-test")
    sandbox.git.checkout_branch(workdir, "feature/sandbox-test")
    print(sandbox.git.status(workdir).current_branch)
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
  });

  try {
    const repoUrl = 'https://gitee.com/aliyunfc/Hello-World.git';
    const workdir = '/tmp/git-repo';

    await sbx.git.clone(repoUrl, { path: workdir, depth: 1 });
    const status = await sbx.git.status(workdir);
    console.log(status.currentBranch, status.isClean);

    await sbx.git.configureUser('Sandbox Bot', 'sandbox@example.com', { cwd: workdir });
    await sbx.files.write(`${workdir}/sandbox-note.txt`, 'created in sandbox\n');
    await sbx.git.add(workdir);
    await sbx.git.commit(workdir, 'Add sandbox note');

    await sbx.git.createBranch(workdir, 'feature/sandbox-test');
    await sbx.git.checkoutBranch(workdir, 'feature/sandbox-test');
    const branchStatus = await sbx.git.status(workdir);
    console.log(branchStatus.currentBranch);
  } finally {
    await sbx.kill();
  }
}

main();

如果要访问私有仓库,建议使用短期 Token,并注意不要把 Token 写入 Git 历史或日志。

九、动态端口访问

沙箱内启动 HTTP 服务后,可以通过 get_host(port) / getHost(port) 获取外部访问地址。

import time

from e2b import Sandbox


HTTP_PORT = 8080
sandbox = None

try:
    sandbox = Sandbox.create(
        api_key=E2B_API_KEY,
        timeout=300,
        allow_internet_access=True,
        **E2B_CONN_OPTS,
    )

    sandbox.files.write(
        "/tmp/www/index.html",
        "<html><body><h1>Hello from sandbox!</h1></body></html>",
    )
    process = sandbox.commands.run(
        f"python3 -m http.server {HTTP_PORT}",
        cwd="/tmp/www",
        background=True,
    )
    print(f"http server pid: {process.pid}")

    time.sleep(2)
    host = sandbox.get_host(HTTP_PORT)
    print(f"external url: https://{host}")

    result = sandbox.commands.run(f"curl -s http://localhost:{HTTP_PORT}/index.html")
    print(result.stdout)
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

const HTTP_PORT = 8080;

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
    allowInternetAccess: true,
  });

  try {
    await sbx.files.write(
      '/tmp/www/index.html',
      '<html><body><h1>Hello from sandbox!</h1></body></html>',
    );
    const proc = await sbx.commands.run(
      `python3 -m http.server ${HTTP_PORT}`,
      { cwd: '/tmp/www', background: true },
    );
    console.log(`http server pid: ${proc.pid}`);

    await sleep(2000);
    const host = sbx.getHost(HTTP_PORT);
    console.log(`external url: https://${host}`);

    const result = await sbx.commands.run(
      `curl -s http://localhost:${HTTP_PORT}/index.html`,
    );
    console.log(result.stdout);
  } finally {
    await sbx.kill();
  }
}

main();

适合场景:

  • 临时预览 Web 页面。
  • 暴露沙箱内调试服务。
  • 让外部工具通过 HTTPS 访问沙箱内 HTTP 服务。

十、列出模板

除了 CLI 的 e2b template list,也可以直接调用控制面 API 获取模板列表。

import requests


api_url = require_env("E2B_API_URL")
api_key = require_env("E2B_API_KEY")

response = requests.get(
    f"{api_url}/templates",
    headers={
        "X-API-Key": api_key,
        "Accept": "application/json",
    },
    timeout=30,
)
response.raise_for_status()

for template in response.json():
    names = template.get("names") or template.get("aliases") or []
    print({
        "name": ", ".join(names) if names else "N/A",
        "templateID": template.get("templateID"),
        "buildStatus": template.get("buildStatus"),
        "cpuCount": template.get("cpuCount"),
        "memoryMB": template.get("memoryMB"),
    })
import 'dotenv/config';

const E2B_API_KEY = process.env.E2B_API_KEY!;
const E2B_DOMAIN = process.env.E2B_DOMAIN!;
const E2B_API_URL = `https://api.${E2B_DOMAIN}`;

async function main() {
  const response = await fetch(`${E2B_API_URL}/templates`, {
    headers: {
      'X-API-Key': E2B_API_KEY,
      'Accept': 'application/json',
    },
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  }

  const templates = await response.json() as any[];
  for (const template of templates) {
    const names = template.names ?? template.aliases ?? [];
    console.log({
      name: names.length > 0 ? names.join(', ') : 'N/A',
      templateID: template.templateID,
      buildStatus: template.buildStatus,
      cpuCount: template.cpuCount,
      memoryMB: template.memoryMB,
    });
  }
}

main();

这个方式适合做控制台、巡检脚本或 CI 校验。

十一、VPC 网络配置

如果沙箱需要访问 VPC 内资源,可以在 metadata 中传入 VPC 配置。

import json

from e2b import Sandbox


vpc_config = {
    "vpcId": "vpc-xxxxxxxx",
    "securityGroupId": "sg-xxxxxxxx",
    "vSwitchIds": ["vsw-xxxxxxxx"],
}

sandbox = None
try:
    sandbox = Sandbox.create(
        api_key=E2B_API_KEY,
        timeout=300,
        metadata={
            "fc.sandbox.network.vpc": json.dumps(vpc_config),
        },
        **E2B_CONN_OPTS,
    )

    result = sandbox.commands.run("ip route && echo 'VPC config applied'")
    print(result.stdout)
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

const vpcConfig = {
  vpcId: 'vpc-xxxxxxxx',
  securityGroupId: 'sg-xxxxxxxx',
  vSwitchIds: ['vsw-xxxxxxxx'],
};

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
    metadata: {
      'fc.sandbox.network.vpc': JSON.stringify(vpcConfig),
    },
  });

  try {
    const result = await sbx.commands.run("ip route && echo 'VPC config applied'");
    console.log(result.stdout);
  } finally {
    await sbx.kill();
  }
}

main();

注意事项:

  • VPC、交换机、安全组应位于沙箱服务支持的地域。
  • 安全组需要允许沙箱访问目标服务端口。
  • 如果结合 NAS,一般必须同时配置 VPC。

十二、挂载 OSS

OSS 挂载适合把对象存储作为沙箱内的工作目录或结果输出目录。

import json
from datetime import datetime

from e2b import Sandbox


mount_dir = "/mnt/oss"
oss_config = {
    "mountPoints": [
        {
            "bucketName": "your-bucket-name",
            "mountDir": mount_dir,
            "bucketPath": "/",
            "endpoint": "http://oss-cn-beijing-internal.aliyuncs.com",
            "readOnly": False,
        }
    ]
}

role_arn = "acs:ram::<account-id>:role/<role-name>"

sandbox = None
try:
    sandbox = Sandbox.create(
        api_key=E2B_API_KEY,
        timeout=300,
        metadata={
            "fc.sandbox.storage.oss": json.dumps(oss_config),
            "fc.sandbox.auth.role": role_arn,
        },
        **E2B_CONN_OPTS,
    )

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = f"{mount_dir}/e2b-test-{timestamp}.txt"
    sandbox.files.write(path, f"Hello from OSS sandbox at {timestamp}\n")
    print(sandbox.files.read(path))
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

const mountDir = '/mnt/oss';
const ossConfig = {
  mountPoints: [
    {
      bucketName: 'your-bucket-name',
      mountDir,
      bucketPath: '/',
      endpoint: 'http://oss-cn-beijing-internal.aliyuncs.com',
      readOnly: false,
    },
  ],
};
const roleArn = 'acs:ram::<account-id>:role/<role-name>';

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
    metadata: {
      'fc.sandbox.storage.oss': JSON.stringify(ossConfig),
      'fc.sandbox.auth.role': roleArn,
    },
  });

  try {
    const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 15);
    const path = `${mountDir}/e2b-test-${timestamp}.txt`;
    await sbx.files.write(path, `Hello from OSS sandbox at ${timestamp}\n`);
    console.log(await sbx.files.read(path));
  } finally {
    await sbx.kill();
  }
}

main();

注意事项:

  • role_arn 需要具备对应 Bucket 的读写权限。
  • 内网 endpoint 需要与沙箱地域匹配。
  • 如果只读访问,把 readOnly 设置为 True / true

十三、挂载 NAS

NAS 挂载适合需要 POSIX 文件语义、目录共享或大规模文件读写的场景。NAS 通常需要和 VPC 配合使用。

import json
from datetime import datetime

from e2b import Sandbox


nas_mount_dir = "/mnt/nas"
nas_config = {
    "mountPoints": [
        {
            "serverAddr": "<nas-mount-target>:/",
            "mountDir": nas_mount_dir,
        }
    ]
}

vpc_config = {
    "vpcId": "vpc-xxxxxxxx",
    "securityGroupId": "sg-xxxxxxxx",
    "vSwitchIds": ["vsw-xxxxxxxx"],
}

sandbox = None
try:
    sandbox = Sandbox.create(
        api_key=E2B_API_KEY,
        timeout=300,
        metadata={
            "fc.sandbox.storage.nas": json.dumps(nas_config),
            "fc.sandbox.network.vpc": json.dumps(vpc_config),
        },
        **E2B_CONN_OPTS,
    )

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    test_dir = f"{nas_mount_dir}/e2b-test-{timestamp}"
    sandbox.commands.run(f"sudo mkdir -p {test_dir}")
    sandbox.commands.run(f"sudo chown user:user {test_dir}")

    path = f"{test_dir}/hello.txt"
    sandbox.files.write(path, "Hello from NAS sandbox\n")
    print(sandbox.files.read(path))
finally:
    if sandbox is not None:
        sandbox.kill()
import 'dotenv/config';
import { Sandbox } from 'e2b';

const nasMountDir = '/mnt/nas';
const nasConfig = {
  mountPoints: [
    {
      serverAddr: '<nas-mount-target>:/',
      mountDir: nasMountDir,
    },
  ],
};

const vpcConfig = {
  vpcId: 'vpc-xxxxxxxx',
  securityGroupId: 'sg-xxxxxxxx',
  vSwitchIds: ['vsw-xxxxxxxx'],
};

async function main() {
  const sbx = await Sandbox.create({
    apiKey: process.env.E2B_API_KEY,
    timeoutMs: 300_000,
    metadata: {
      'fc.sandbox.storage.nas': JSON.stringify(nasConfig),
      'fc.sandbox.network.vpc': JSON.stringify(vpcConfig),
    },
  });

  try {
    const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 15);
    const testDir = `${nasMountDir}/e2b-test-${timestamp}`;
    await sbx.commands.run(`sudo mkdir -p ${testDir}`);
    await sbx.commands.run(`sudo chown user:user ${testDir}`);

    const path = `${testDir}/hello.txt`;
    await sbx.files.write(path, 'Hello from NAS sandbox\n');
    console.log(await sbx.files.read(path));
  } finally {
    await sbx.kill();
  }
}

main();

注意事项:

  • NAS 挂载点、VPC、交换机、安全组需要在同一网络链路内可达。
  • 安全组和 NAS 权限组需要放通 NFS 访问。
  • 生产环境建议把挂载目录和业务临时目录分开,避免误删共享数据。

十四、浏览器自动化

沙箱支持运行浏览器自动化任务。通过在沙箱内启动内置浏览器服务,暴露 CDP(Chrome DevTools Protocol)端点,外部工具(如 Playwright)可以通过 WebSocket 连接远端浏览器进行自动化操作。

Note

浏览器自动化需要使用预装了浏览器和 browsertool 的自定义镜像。以下示例会先从镜像构建模板,再创建沙箱启动浏览器服务。

import 'dotenv/config';
import { chromium } from 'playwright';
import { Sandbox, Template } from 'e2b';

const E2B_API_KEY = process.env.E2B_API_KEY!;
const image = 'your-registry.cn-beijing.cr.aliyuncs.com/namespace/browser-image:tag';
const templateName = `browser-demo-${Math.floor(Date.now() / 1000)}`;

const BROWSERTOOL_PORT = 3000;
const PC_CONFIG = '/etc/sandbox/config/process-compose.browsertool.yaml';

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function startBrowsertool(sbx: Sandbox) {
  await sbx.commands.run(
    'install -d -m 1777 /tmp/.X11-unix && rm -f /tmp/.X1-lock && mkdir -p /run/user/1000/dconf',
    { user: 'root' },
  );

  const chromePathResult = await sbx.commands.run('cat /etc/browsertool/chrome-path', { user: 'root' });
  const chromePath = chromePathResult.stdout.trim();

  await sbx.commands.run(
    `process-compose up -f ${PC_CONFIG} --tui=false --no-server > /tmp/browsertool-pc.log 2>&1`,
    {
      envs: { SXBT_BROWSER_CHROMIUM_PATH: chromePath },
      background: true,
      user: 'root',
    },
  );

  const deadline = Date.now() + 60000;
  while (Date.now() < deadline) {
    const r = await sbx.commands.run(
      `curl -sS -m 2 -o /dev/null -w '%{http_code}' http://localhost:${BROWSERTOOL_PORT}/health || true`,
      { user: 'root' },
    );
    if (r.stdout.trim() === '200') return;
    await sleep(2000);
  }
  throw new Error('browsertool 在 60s 内未就绪');
}

async function main() {
  // 1. 构建模板
  const registryTemplate = Template().fromImage(image);
  const buildInfo = await Template.build(registryTemplate, templateName, {
    apiKey: E2B_API_KEY,
    cpuCount: 2,
    memoryMB: 2048,
  });

  // 2. 创建沙箱
  const sbx = await Sandbox.create({
    template: buildInfo.name,
    apiKey: E2B_API_KEY,
    timeoutMs: 600_000,
    allowInternetAccess: true,
  });

  try {
    // 3. 启动浏览器服务
    await startBrowsertool(sbx);

    // 4. 获取 CDP WebSocket 端点
    const host = sbx.getHost(BROWSERTOOL_PORT);
    const cdpWsUrl = `wss://${host}/ws/automation`;
    console.log(`CDP endpoint: ${cdpWsUrl}`);

    // 5. 通过 Playwright 连接远端浏览器
    const headers: Record<string, string> = {};
    const token = sbx.trafficAccessToken;
    if (token) headers['X-Access-Token'] = token;

    const browser = await chromium.connectOverCDP(cdpWsUrl, { headers });
    const context = browser.contexts().length > 0 ? browser.contexts()[0] : await browser.newContext();
    const page = await context.newPage();

    try {
      await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 30000 });
      console.log(`page title: ${await page.title()}`);
    } finally {
      await page.close();
      await browser.close();
    }
  } finally {
    await sbx.kill();
  }
}

main();

关键步骤:

  1. 构建模板:使用预装浏览器的镜像构建模板。
  2. 创建沙箱:设置较长的 timeoutMsallowInternetAccess: true
  3. 启动浏览器服务:通过 process-compose 启动 browsertool,等待 /health 就绪。
  4. 获取 CDP 端点:通过 getHost(port) 获取外部可达的 WebSocket 地址。
  5. 连接浏览器:使用 Playwright 的 connectOverCDP 连接远端浏览器。

注意事项:

  • 公网网关要求通过 X-Access-Token 头认证 WebSocket 连接,使用 sbx.trafficAccessToken 获取。
  • 浏览器镜像体积较大,首次构建模板可能需要几分钟。
  • 建议设置较长的沙箱超时时间(如 600 秒),浏览器自动化任务通常耗时较长。

十五、能力兼容概览

当前服务对 E2B Python SDK 和 TypeScript SDK 的核心能力兼容情况如下:

SDK 能力

能力 状态 说明
Sandbox 创建、连接、列表、销毁 支持 支持 template / timeout / envs / metadata
sandbox.set_timeout() / sandbox.setTimeout() 支持 实例方法和静态方法均支持
sandbox.pause() / sandbox.resume() 支持 需要特定地域支持
sandbox.getHost(port) / sandbox.downloadUrl / sandbox.uploadUrl 支持 客户端生成 URL
Commands 运行命令、后台进程、PTY 支持 包括 run / list / connect / sendStdin / kill
Filesystem 读写、列目录、创建目录、移动、删除、监听 支持 完整文件系统操作
Code Interpreter Python / JavaScript 执行和上下文 支持 runCode / createContext / 上下文生命周期管理
Git 操作 支持 clone / status / add / commit / branch 等
Template 创建、构建、列表、标签和别名 支持 v1/v2/v3 全版本
CLI sandbox create/list/killtemplate list 支持 E2B CLI 核心命令
Metrics、Logs 部分 Stub 兼容调用但不建议依赖完整语义,控制台视角完全兼容
Snapshots 暂不支持 返回 501 Not Implemented
Volume API、API Key 管理、Access Token 管理、Team API 暂不支持

API 兼容汇总

维度 支持情况
控制面 API 24/44 完整实现,6 个 Stub,14 个不支持
数据面 API 21/21 全部完整实现(进程管理 7 + 文件系统 7 + Code Interpreter 5 + 健康检查 2)
认证方式 X-API-KeyAuthorization: Bearer 均支持

FC metadata 扩展能力

以下能力通过 metadata 参数传入,是函数计算云沙箱的扩展能力,不属于 E2B 官方 SDK:

metadata key 说明
fc.sandbox.network.vpc VPC 网络配置
fc.sandbox.storage.oss OSS 对象存储挂载
fc.sandbox.storage.nas NAS 文件存储挂载
fc.sandbox.auth.role RAM Role ARN,用于 OSS 等服务的权限

如果业务依赖不支持的 E2B 官方能力,建议优先使用 FC metadata 扩展能力、OSS、NAS 或应用侧控制面逻辑替代。