构建和管理模板

更新时间:
复制 MD 格式

云沙箱模板支持通过阿里云 OpenAPI 创建、查询和删除,CreateTemplate 一次完成模板创建和首次构建,构建异步执行,轮询 GetTemplate 获取构建状态,模板就绪后即可用于创建沙箱。本文介绍如何安装 SDK、配置权限、创建模板并构建,以及如何配置镜像源、运行时网络和日志,帮助您以自动化方式构建和管理沙箱模板。

前提条件

  • 已开通函数计算和云沙箱

  • 已在云沙箱控制台创建 Team,并获取 Team ID。

  • 准备阿里云账号或 RAM 用户,并为调用身份配置模板 API 权限,参见下文权限配置

  • 确认模板所在地域,例如北京地域为 cn-beijing,全部地域参见支持地域

  • 使用 E2B SDK 本地构建时,获取连接参数 E2B_API_KEYE2B_API_URLE2B_DOMAIN,SDK 会自动读取这三个环境变量。参数详情见 E2B SDK 接入参数说明

权限配置

调用模板 API 的 RAM 身份需要以下权限:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "fcsandbox:CreateTemplate",
        "fcsandbox:GetTemplate",
        "fcsandbox:ListTemplates",
        "fcsandbox:DeleteTemplate"
      ],
      "Resource": "*"
    }
  ]
}

支持按 Team 精确授权 Template 资源:

acs:fcsandbox:<region>:<account-id>:teams/<team-id>/templates/*

权限策略的配置方法参见配置 RAM 用户权限。E2B SDK 本地构建通过 API Key 鉴权,不涉及 RAM 权限配置。

安装 SDK

云沙箱 OpenAPI 当前支持 Python、TypeScript、Go 和 Java SDK。支持范围、安装方式和版本以阿里云 SDK & API 说明为准:

Python

python3 -m venv .venv
source .venv/bin/activate
pip install "alibabacloud_fcsandbox20260509"

TypeScript

npm install @alicloud/fcsandbox20260509 @alicloud/openapi-client

Go

go mod init pop-template-demo
go get github.com/alibabacloud-go/fcsandbox-20260509

Java

Maven 依赖(版本以 SDK 说明页面为准),在工程 pom.xml 中添加:

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>fcsandbox20260509</artifactId>
    <version>1.4.0</version>
</dependency>

新建工程时可使用以下最小 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>demo</groupId>
    <artifactId>pop-template-demo</artifactId>
    <version>1.0.0</version>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>fcsandbox20260509</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>
</project>

示例代码保存为工程下的 src/main/java/CreateTemplateDemo.java

运行示例前设置以下环境变量:

export ALIBABA_CLOUD_ACCESS_KEY_ID="<YOUR-ACCESS-KEY-ID>"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="<YOUR-ACCESS-KEY-SECRET>"
# 使用 STS 临时凭证时还需要设置:
# export ALIBABA_CLOUD_SECURITY_TOKEN="<YOUR-SECURITY-TOKEN>"

export FCSANDBOX_REGION_ID="cn-beijing"
export FCSANDBOX_ENDPOINT="fcsandbox.cn-beijing.aliyuncs.com"
export FCSANDBOX_TEAM_ID="<team-id>"

FCSANDBOX_ENDPOINT 不包含 https://。如果 SDK 已内置目标地域的 Endpoint,可以不设置该变量,由 SDK 根据 FCSANDBOX_REGION_ID 解析。

创建模板并构建

请求参数、返回参数和错误码以 OpenAPI 门户的 CreateTemplate - 创建模板 为准。

请求体 CreateTemplateInput 包含 nameteam_idruntime_configbuild_config 四个字段,模板创建后立即开始首次构建:

  • runtime_config:最终模板的运行规格。cpu(核数)、memory_size(MB)、disk_size(MB)按需填写,缺省时使用默认规格;sandbox_config.image 为必填的构建源镜像。

  • build_config:构建方式。整段缺省时按源镜像自动推导——云沙箱官方镜像直接使用,不再构建;其他镜像自动执行 copy(处理并推送目标镜像)和 envdInject(注入沙箱运行依赖)。

镜像要求与各镜像源的约束和传参写法参见下文镜像要求镜像源

以下示例使用北京地域和云沙箱官方镜像(无需自备 ACR EE 实例)创建模板,轮询构建状态至就绪后删除模板:

Python

import os
import time

from alibabacloud_fcsandbox20260509 import models
from alibabacloud_fcsandbox20260509.client import Client as FCSandboxClient
from alibabacloud_tea_openapi import models as open_api_models


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


config = open_api_models.Config(
    access_key_id=require_env("ALIBABA_CLOUD_ACCESS_KEY_ID"),
    access_key_secret=require_env("ALIBABA_CLOUD_ACCESS_KEY_SECRET"),
    security_token=os.environ.get("ALIBABA_CLOUD_SECURITY_TOKEN"),
    region_id=require_env("FCSANDBOX_REGION_ID"),
)
if os.environ.get("FCSANDBOX_ENDPOINT"):
    config.endpoint = os.environ["FCSANDBOX_ENDPOINT"]

pop_client = FCSandboxClient(config)
team_id = require_env("FCSANDBOX_TEAM_ID")
from_image = "fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49"

# 1. 创建模板并提交首次构建,返回 templateID。
response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                sandbox_config=models.CreateTemplateSandboxConfig(image=from_image),
            ),
        )
    )
)
template_id = response.body.template_id
print(f"created templateID: {template_id}, request_id: {response.body.request_id}")

# 2. 轮询 GetTemplate 直到终态。
deadline = time.time() + 900
while True:
    got = pop_client.get_template(template_id, models.GetTemplateRequest(team_id=team_id))
    state = got.body.status.state
    print(f"state: {state}")
    if state in ("ready", "error"):
        break
    if time.time() > deadline:
        raise SystemExit("构建超时")
    time.sleep(5)
if state == "error":
    raise SystemExit(f"构建失败: {got.body.status.reason.message}")

# 3. 模板就绪后即可用于创建沙箱;不再使用时删除模板清理资源。
pop_client.delete_template(template_id, models.DeleteTemplateRequest(team_id=team_id))
print(f"deleted templateID: {template_id}")

TypeScript

import * as process from 'process';
import FCSandbox20260509, * as $FCSandbox20260509 from '@alicloud/fcsandbox20260509';
import * as $OpenApi from '@alicloud/openapi-client';

function requireEnv(name: string): string {
  const value = (process.env[name] ?? '').trim();
  if (!value) {
    throw new Error(`缺少环境变量: ${name}`);
  }
  return value;
}

async function main() {
  const config = new $OpenApi.Config({
    accessKeyId: requireEnv('ALIBABA_CLOUD_ACCESS_KEY_ID'),
    accessKeySecret: requireEnv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
    securityToken: (process.env['ALIBABA_CLOUD_SECURITY_TOKEN'] ?? '').trim() || undefined,
    regionId: requireEnv('FCSANDBOX_REGION_ID'),
    endpoint: (process.env['FCSANDBOX_ENDPOINT'] ?? '').trim() || undefined,
  });

  const popClient = new FCSandbox20260509(config);
  const teamID = requireEnv('FCSANDBOX_TEAM_ID');
  const fromImage = 'fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49';

  // 1. 创建模板并提交首次构建,返回 templateID。
  const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
    body: new $FCSandbox20260509.CreateTemplateInput({
      name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
      teamID,
      runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
        sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({ image: fromImage }),
      }),
    }),
  }));
  const templateID = created.body.templateID;
  console.log(`created templateID: ${templateID}, request_id: ${created.body.requestId}`);

  // 2. 轮询 GetTemplate 直到终态。
  const deadline = Date.now() + 900_000;
  let state = '';
  let reasonMessage = '';
  while (true) {
    const got = await popClient.getTemplate(templateID,
      new $FCSandbox20260509.GetTemplateRequest({ teamID }));
    state = got.body.status.state;
    console.log(`state: ${state}`);
    reasonMessage = got.body.status.reason?.message ?? '';
    if (state === 'ready' || state === 'error') {
      break;
    }
    if (Date.now() > deadline) {
      throw new Error('构建超时');
    }
    await new Promise(resolve => setTimeout(resolve, 5000));
  }
  if (state === 'error') {
    throw new Error(`构建失败: ${reasonMessage}`);
  }

  // 3. 模板就绪后即可用于创建沙箱;不再使用时删除模板清理资源。
  await popClient.deleteTemplate(templateID,
    new $FCSandbox20260509.DeleteTemplateRequest({ teamID }));
  console.log(`deleted templateID: ${templateID}`);
}

main();

Go

package main

import (
    "fmt"
    "os"
    "strings"
    "time"

    openapiutil "github.com/alibabacloud-go/darabonba-openapi/v2/utils"
    fcsandbox20260509 "github.com/alibabacloud-go/fcsandbox-20260509/client"
    "github.com/alibabacloud-go/tea/tea"
)

// requireEnv 读取必填环境变量,缺失时直接 panic。
func requireEnv(name string) string {
    value := strings.TrimSpace(os.Getenv(name))
    if value == "" {
        panic(fmt.Sprintf("缺少环境变量: %s", name))
    }
    return value
}

func main() {
    config := &openapiutil.Config{
        AccessKeyId:     tea.String(requireEnv("ALIBABA_CLOUD_ACCESS_KEY_ID")),
        AccessKeySecret: tea.String(requireEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")),
        RegionId:        tea.String(requireEnv("FCSANDBOX_REGION_ID")),
    }
    if token := strings.TrimSpace(os.Getenv("ALIBABA_CLOUD_SECURITY_TOKEN")); token != "" {
        config.SecurityToken = tea.String(token)
    }
    if endpoint := strings.TrimSpace(os.Getenv("FCSANDBOX_ENDPOINT")); endpoint != "" {
        config.Endpoint = tea.String(endpoint)
    }

    popClient, err := fcsandbox20260509.NewClient(config)
    if err != nil {
        panic(err)
    }
    teamID := requireEnv("FCSANDBOX_TEAM_ID")
    fromImage := "fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49"

    // 1. 创建模板并提交首次构建,返回 templateID。
    created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
        Body: &fcsandbox20260509.CreateTemplateInput{
            Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
            TeamID: tea.String(teamID),
            RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
                SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                    Image: tea.String(fromImage),
                },
            },
        },
    })
    if err != nil {
        panic(err)
    }
    templateID := tea.StringValue(created.Body.TemplateID)
    fmt.Printf("created templateID: %s, request_id: %s\n",
        templateID, tea.StringValue(created.Body.RequestId))

    // 2. 轮询 GetTemplate 直到终态。
    deadline := time.Now().Add(900 * time.Second)
    state := ""
    reasonMessage := ""
    for {
        got, err := popClient.GetTemplate(tea.String(templateID), &fcsandbox20260509.GetTemplateRequest{
            TeamID: tea.String(teamID),
        })
        if err != nil {
            panic(err)
        }
        state = tea.StringValue(got.Body.Status.State)
        fmt.Printf("state: %s\n", state)
        if got.Body.Status.Reason != nil {
            reasonMessage = tea.StringValue(got.Body.Status.Reason.Message)
        }
        if state == "ready" || state == "error" {
            break
        }
        if time.Now().After(deadline) {
            panic("构建超时")
        }
        time.Sleep(5 * time.Second)
    }
    if state == "error" {
        panic(fmt.Sprintf("构建失败: %s", reasonMessage))
    }

    // 3. 模板就绪后即可用于创建沙箱;不再使用时删除模板清理资源。
    if _, err := popClient.DeleteTemplate(tea.String(templateID), &fcsandbox20260509.DeleteTemplateRequest{
        TeamID: tea.String(teamID),
    }); err != nil {
        panic(err)
    }
    fmt.Printf("deleted templateID: %s\n", templateID)
}

Java

import com.aliyun.fcsandbox20260509.Client;
import com.aliyun.fcsandbox20260509.models.CreateTemplateBuildConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateCopyAction;
import com.aliyun.fcsandbox20260509.models.CreateTemplateInput;
import com.aliyun.fcsandbox20260509.models.CreateTemplateLogConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateRegistryAuthConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateRegistryCertConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateRegistryConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateRegistryNetworkConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateRequest;
import com.aliyun.fcsandbox20260509.models.CreateTemplateRuntimeConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateSandboxConfig;
import com.aliyun.fcsandbox20260509.models.CreateTemplateVPCConfig;
import com.aliyun.fcsandbox20260509.models.DeleteTemplateRequest;
import com.aliyun.fcsandbox20260509.models.GetTemplateRequest;
import com.aliyun.fcsandbox20260509.models.GetTemplateResponse;
import com.aliyun.fcsandbox20260509.models.ListTemplatesRequest;
import com.aliyun.fcsandbox20260509.models.ListTemplatesResponse;
import com.aliyun.fcsandbox20260509.models.PublicTemplate;
import com.aliyun.teaopenapi.models.Config;

public class CreateTemplateDemo {

    private static String requireEnv(String name) {
        String value = System.getenv(name);
        if (value == null || value.isBlank()) {
            throw new RuntimeException("缺少环境变量: " + name);
        }
        return value.trim();
    }

    public static void main(String[] args) throws Exception {
        Config config = new Config()
                .setAccessKeyId(requireEnv("ALIBABA_CLOUD_ACCESS_KEY_ID"))
                .setAccessKeySecret(requireEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"))
                .setRegionId(requireEnv("FCSANDBOX_REGION_ID"));
        String securityToken = System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN");
        if (securityToken != null && !securityToken.isBlank()) {
            config.setSecurityToken(securityToken.trim());
        }
        String endpoint = System.getenv("FCSANDBOX_ENDPOINT");
        if (endpoint != null && !endpoint.isBlank()) {
            config.setEndpoint(endpoint.trim());
        }

        Client popClient = new Client(config);
        String teamID = requireEnv("FCSANDBOX_TEAM_ID");
        String fromImage = "fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49";

        // 1. 创建模板并提交首次构建,返回 templateID。
        CreateTemplateInput body = new CreateTemplateInput()
                .setName("pop-demo-" + System.currentTimeMillis() / 1000)
                .setTeamID(teamID)
                .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                        .setSandboxConfig(new CreateTemplateSandboxConfig()
                                .setImage(fromImage)));
        String templateID = popClient
                .createTemplate(new CreateTemplateRequest().setBody(body))
                .getBody().getTemplateID();
        System.out.printf("created templateID: %s%n", templateID);

        // 2. 轮询 GetTemplate 直到终态。
        long deadline = System.currentTimeMillis() + 900_000;
        String state = "";
        GetTemplateResponse got = null;
        while (true) {
            got = popClient.getTemplate(templateID,
                    new GetTemplateRequest().setTeamID(teamID));
            state = got.getBody().getStatus().getState();
            System.out.printf("state: %s%n", state);
            if ("ready".equals(state) || "error".equals(state)) {
                break;
            }
            if (System.currentTimeMillis() > deadline) {
                throw new RuntimeException("构建超时");
            }
            Thread.sleep(5000);
        }
        if ("error".equals(state)) {
            throw new RuntimeException("构建失败: "
                    + got.getBody().getStatus().getReason().getMessage());
        }

        // 3. 模板就绪后即可用于创建沙箱;不再使用时删除模板清理资源。
        popClient.deleteTemplate(templateID,
                new DeleteTemplateRequest().setTeamID(teamID));
        System.out.printf("deleted templateID: %s%n", templateID);
    }
}

运行示例:

Python

python create_template.py

TypeScript

npx tsx create_template.ts

Go

go mod tidy
go run main.go

Java

mvn compile exec:java -Dexec.mainClass="CreateTemplateDemo"

构建为异步执行,轮询耗时取决于镜像大小,通常在数十秒到数分钟之间。

镜像要求

构建自定义镜像模板有两条路径,镜像要求相同,但凭证与网络配置能力不同:通过 OpenAPI 构建,调用 CreateTemplate 由服务端完成构建,适合自动化脚本、基础设施即代码(IaC)和平台化集成场景;通过 E2B SDK 本地构建,在本地运行 Template.build 提交构建,适合首次验证和个人开发。

推荐基础镜像:Ubuntu 20.04、Debian 12(bookworm)及以上(仅作推荐,不强制发行版)。自定义镜像需满足下表要求,按“硬性 / 条件 / 推荐”分级:

级别

要求

不满足时影响

硬性

架构为 linux/amd64;若为多架构镜像,其 manifest 列表必须包含 linux/amd64 变体

模板构建直接失败

硬性

ACR 镜像不得开启镜像加速选项

模板构建直接失败

条件

镜像内存在固定路径 /bin/bash(而非仅在 PATH 中可找到 Bash)

commands.run、PTY 失败

硬性

/etc/passwd/etc/group 为标准文件且可写

沙箱无法初始化默认用户,容器启动失败

条件

PATH 中存在 python3

Python run_code 不可用

条件

PATH 中存在 node

JavaScript run_code 不可用

条件

按需提供:gitopenssh-client、CA 证书、编译工具链

对应的 Git、SSH、HTTPS、编译安装操作不可用

推荐

系统 PATH 至少包含 /usr/local/bin/usr/bin/bin

登录 Shell 常用命令可能找不到

向自己的镜像仓库推送镜像时,不要向同一个 tag 推送内容不同的镜像。每个不同的镜像都必须使用新的唯一 tag,例如使用版本号、日期或提交 ID,并让模板固定引用对应 tag。

镜像源

构建源镜像分为四类:公共镜像、ACR 企业版、公网 OCI 仓库、VPC 内私有 OCI 仓库。每类的准备、约束与传参写法如下;示例均为完整的创建模板调用,可直接替换本文“创建模板并构建”示例中的第 1 步,其余步骤(轮询、删除)保持不变。

公共镜像

使用云沙箱官方镜像无需准备 ACR EE 实例,无需凭证与网络配置。官方镜像在全部支持地域均有提供,地址格式为 fc-e2b-registry.<地域>.cr.aliyuncs.com/runtime/<镜像名>:<tag>,地域清单参见支持地域。以北京地域为例:

fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/base:v0.0.49
fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49

约束:

  • 只能引用已发布的官方 tag,不能向官方仓库推送镜像。

  • build_config 缺省时官方镜像直接使用,不再触发构建。

  • 官方镜像已满足上文镜像要求,本文创建模板并构建示例即使用该方式。

  • 镜像地址的地域须与 FCSANDBOX_REGION_ID 一致。

传参示例:

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49",
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49',
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49"),
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49")));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

ACR 企业版(ACR EE)

前置准备:

  1. 在与云沙箱相同的阿里云账号和地域下,创建 ACR EE 实例(经济版暂不支持)。

  2. 为 ACR EE 实例添加专有网络,确保云沙箱可以通过 VPC 拉取镜像。

  3. 创建命名空间与镜像仓库并推送镜像,得到支持 VPC 访问的内网镜像地址,例如:

test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1

网络约束:

  • ACR EE 实例已至少绑定一个专有网络 VPC。

  • 该 VPC 下至少有一个 vSwitch 位于函数计算支持的可用区。

  • ACR EE 镜像仓库、VPC、vSwitch 与云沙箱需位于同一地域,本文示例默认使用北京地域。

  • ACR EE 访问控制与 VPC 配置已允许对应网络访问。

  • VPC 网段需使用 RFC 1918 规定的私有地址段,即 10.0.0.0/8172.16.0.0/12192.168.0.0/16。不支持公网私用,详情请参见VPC 常见问题

  • VPC 下需至少配置一个自建的安全组,且该安全组的出方向规则需放行 443/TCP 端口。

函数计算支持的可用区会随地域和产品能力调整,最新列表请参考配置网络

常见失败:

  • 镜像拉取失败:依次检查镜像地址、地域、命名空间、仓库权限、VPC 绑定和访问控制配置。使用私有 ACR EE 镜像时,网络配置错误比 SDK 参数错误更常见。

  • 拉取网络 vSwitch 可用区不支持:处理方式参见下文常见问题vSwitch 所在可用区不支持

拉取方式:最简单的方式是只传 image,平台按镜像地址推导 ACR EE 实例与仓库类型,使用实例临时授权拉取,并从实例绑定的 VPC 推导拉取网络。需要显式指定实例、凭证或拉取网络时,再传 acr_instance_idregistry_config.auth_configregistry_config.network_config;实例推导出的 vSwitch 落在不支持的可用区导致拉取失败时,必须用 network_config 显式指定 VPC、vSwitch 和安全组三项配置。

传参示例一:只传 image,实例、凭证与拉取网络全部缺省推导:

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1",
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1',
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1"),
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1")));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

传参示例二:acr_instance_idauth_confignetwork_config 都传,显式指定实例、凭证与拉取网络:

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1",
                    acr_instance_id="cri-xxxxxx",
                    registry_config=models.CreateTemplateRegistryConfig(
                        auth_config=models.CreateTemplateRegistryAuthConfig(
                            user_name="<username>",
                            password="<password>",
                        ),
                        network_config=models.CreateTemplateRegistryNetworkConfig(
                            vpc_id="vpc-xxxxxx",
                            v_switch_id="vsw-xxxxxx",
                            security_group_id="sg-xxxxxx",
                        ),
                    ),
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1',
        acrInstanceId: 'cri-xxxxxx',
        registryConfig: new $FCSandbox20260509.CreateTemplateRegistryConfig({
          authConfig: new $FCSandbox20260509.CreateTemplateRegistryAuthConfig({
            userName: '<username>',
            password: '<password>',
          }),
          networkConfig: new $FCSandbox20260509.CreateTemplateRegistryNetworkConfig({
            vpcId: 'vpc-xxxxxx',
            vSwitchId: 'vsw-xxxxxx',
            securityGroupId: 'sg-xxxxxx',
          }),
        }),
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image:         tea.String("test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1"),
                AcrInstanceId: tea.String("cri-xxxxxx"),
                RegistryConfig: &fcsandbox20260509.CreateTemplateRegistryConfig{
                    AuthConfig: &fcsandbox20260509.CreateTemplateRegistryAuthConfig{
                        UserName: tea.String("<username>"),
                        Password: tea.String("<password>"),
                    },
                    NetworkConfig: &fcsandbox20260509.CreateTemplateRegistryNetworkConfig{
                        VpcId:           tea.String("vpc-xxxxxx"),
                        VSwitchId:       tea.String("vsw-xxxxxx"),
                        SecurityGroupId: tea.String("sg-xxxxxx"),
                    },
                },
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1")
                        .setAcrInstanceId("cri-xxxxxx")
                        .setRegistryConfig(new CreateTemplateRegistryConfig()
                                .setAuthConfig(new CreateTemplateRegistryAuthConfig()
                                        .setUserName("<username>")
                                        .setPassword("<password>"))
                                .setNetworkConfig(new CreateTemplateRegistryNetworkConfig()
                                        .setVpcId("vpc-xxxxxx")
                                        .setVSwitchId("vsw-xxxxxx")
                                        .setSecurityGroupId("sg-xxxxxx")))));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

公网 OCI 仓库

公网可达、TLS 证书正常的 OCI 仓库,无需 VPC 与证书配置。其中“公网可达”指函数计算能够从对应地域访问到该仓库,而不只是仓库对公网开放了域名。

约束:

  • 只需 auth_config 凭证两项(用户名/密码),无需 network_config;凭证需要对该仓库有拉取权限。

  • build_config 缺省时构建产物会推送回同一仓库的新 tag,因此凭证还需要具备推送权限。

  • network_configcert_config 仅在仓库证书需要跳过校验或有特殊网络要求时追加。

传参示例:

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="registry.example.com/ns/repo:v1",
                    registry_config=models.CreateTemplateRegistryConfig(
                        auth_config=models.CreateTemplateRegistryAuthConfig(
                            user_name="<username>",
                            password="<password>",
                        ),
                    ),
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'registry.example.com/ns/repo:v1',
        registryConfig: new $FCSandbox20260509.CreateTemplateRegistryConfig({
          authConfig: new $FCSandbox20260509.CreateTemplateRegistryAuthConfig({
            userName: '<username>',
            password: '<password>',
          }),
        }),
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("registry.example.com/ns/repo:v1"),
                RegistryConfig: &fcsandbox20260509.CreateTemplateRegistryConfig{
                    AuthConfig: &fcsandbox20260509.CreateTemplateRegistryAuthConfig{
                        UserName: tea.String("<username>"),
                        Password: tea.String("<password>"),
                    },
                },
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("registry.example.com/ns/repo:v1")
                        .setRegistryConfig(new CreateTemplateRegistryConfig()
                                .setAuthConfig(new CreateTemplateRegistryAuthConfig()
                                        .setUserName("<username>")
                                        .setPassword("<password>")))));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

VPC 内私有 OCI 仓库

Registry 域名仅 VPC 内可达的自建私有仓库,例如在 VPC 内的 ECS 上自建的 Harbor。

约束:

  • 仓库必须以 HTTPS 提供服务且监听 443 端口,不支持自定义端口或 HTTP。

  • 必须提供 registry_config.network_config(VPC 三项配置 vpc_id/v_switch_id/security_group_id,同时提供)。

  • 使用自签证书的私有仓库还必须在 cert_config 中声明跳过证书校验(insecure=True),否则构建在拉取源镜像阶段以 x509 证书错误失败。

  • 凭证通过 auth_config 照常传入。

传参示例:

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="registry.internal.example.com/ns/repo:v1",
                    registry_config=models.CreateTemplateRegistryConfig(
                        auth_config=models.CreateTemplateRegistryAuthConfig(
                            user_name="<username>",
                            password="<password>",
                        ),
                        network_config=models.CreateTemplateRegistryNetworkConfig(
                            vpc_id="vpc-xxxxxx",
                            v_switch_id="vsw-xxxxxx",
                            security_group_id="sg-xxxxxx",
                        ),
                        cert_config=models.CreateTemplateRegistryCertConfig(
                            insecure=True,
                        ),
                    ),
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'registry.internal.example.com/ns/repo:v1',
        registryConfig: new $FCSandbox20260509.CreateTemplateRegistryConfig({
          authConfig: new $FCSandbox20260509.CreateTemplateRegistryAuthConfig({
            userName: '<username>',
            password: '<password>',
          }),
          networkConfig: new $FCSandbox20260509.CreateTemplateRegistryNetworkConfig({
            vpcId: 'vpc-xxxxxx',
            vSwitchId: 'vsw-xxxxxx',
            securityGroupId: 'sg-xxxxxx',
          }),
          certConfig: new $FCSandbox20260509.CreateTemplateRegistryCertConfig({
            insecure: true,
          }),
        }),
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("registry.internal.example.com/ns/repo:v1"),
                RegistryConfig: &fcsandbox20260509.CreateTemplateRegistryConfig{
                    AuthConfig: &fcsandbox20260509.CreateTemplateRegistryAuthConfig{
                        UserName: tea.String("<username>"),
                        Password: tea.String("<password>"),
                    },
                    NetworkConfig: &fcsandbox20260509.CreateTemplateRegistryNetworkConfig{
                        VpcId:           tea.String("vpc-xxxxxx"),
                        VSwitchId:       tea.String("vsw-xxxxxx"),
                        SecurityGroupId: tea.String("sg-xxxxxx"),
                    },
                    CertConfig: &fcsandbox20260509.CreateTemplateRegistryCertConfig{
                        Insecure: tea.Bool(true),
                    },
                },
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("registry.internal.example.com/ns/repo:v1")
                        .setRegistryConfig(new CreateTemplateRegistryConfig()
                                .setAuthConfig(new CreateTemplateRegistryAuthConfig()
                                        .setUserName("<username>")
                                        .setPassword("<password>"))
                                .setNetworkConfig(new CreateTemplateRegistryNetworkConfig()
                                        .setVpcId("vpc-xxxxxx")
                                        .setVSwitchId("vsw-xxxxxx")
                                        .setSecurityGroupId("sg-xxxxxx"))
                                .setCertConfig(new CreateTemplateRegistryCertConfig()
                                        .setInsecure(true)))));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

运行时网络

vpc_config 决定模板最终运行时沙箱所在的 VPC(vSwitch 支持多个),与构建阶段拉取源镜像的网络(registry_config.network_config)相互独立,按需分别配置。

关闭公网出口(internet_access=False)后,沙箱内访问公网会失败。传参示例:

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                internet_access=False,
                vpc_config=models.CreateTemplateVPCConfig(
                    vpc_id="vpc-xxxxxx",
                    v_switch_ids=["vsw-xxxxxx"],
                    security_group_id="sg-xxxxxx",
                ),
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49",
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      internetAccess: false,
      vpcConfig: new $FCSandbox20260509.CreateTemplateVPCConfig({
        vpcId: 'vpc-xxxxxx',
        vSwitchIds: ['vsw-xxxxxx'],
        securityGroupId: 'sg-xxxxxx',
      }),
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49',
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            InternetAccess: tea.Bool(false),
            VpcConfig: &fcsandbox20260509.CreateTemplateVPCConfig{
                VpcId:           tea.String("vpc-xxxxxx"),
                VSwitchIds:      []*string{tea.String("vsw-xxxxxx")},
                SecurityGroupId: tea.String("sg-xxxxxx"),
            },
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49"),
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setInternetAccess(false)
                .setVpcConfig(new CreateTemplateVPCConfig()
                        .setVpcId("vpc-xxxxxx")
                        .setVSwitchIds(java.util.Arrays.asList("vsw-xxxxxx"))
                        .setSecurityGroupId("sg-xxxxxx"))
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49")));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

日志配置

沙箱运行日志可通过 runtime_config 中的 log_config 投递到您账号下的 SLS,仅开放 projectlogstore 两项,需与云沙箱位于同一地域:

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                log_config=models.CreateTemplateLogConfig(
                    project="<sls-project>",
                    logstore="<sls-logstore>",
                ),
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49",
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      logConfig: new $FCSandbox20260509.CreateTemplateLogConfig({
        project: '<sls-project>',
        logstore: '<sls-logstore>',
      }),
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49',
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            LogConfig: &fcsandbox20260509.CreateTemplateLogConfig{
                Project:  tea.String("<sls-project>"),
                Logstore: tea.String("<sls-logstore>"),
            },
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49"),
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setLogConfig(new CreateTemplateLogConfig()
                        .setProject("<sls-project>")
                        .setLogstore("<sls-logstore>"))
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49")));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

目标镜像与构建复用

构建非官方镜像时,平台会临时创建一个函数计算函数,由它拉取源镜像、注入云沙箱运行所需的依赖,并推送为目标镜像;模板最终使用这个目标镜像创建沙箱运行环境。该临时函数创建在您自己的账号下,构建期间会产生少量函数计算费用。

目标镜像可以通过 build_config.copy.image 显式指定,例如推送到您镜像仓库的新 tag;目标仓库与源镜像所在仓库相同时无需重复提供目标侧凭证,推送到其他仓库时需要在 build_config.copy.registry_config 显式提供目标仓库的凭证与网络配置。多次构建指定完全相同的目标镜像时,平台会复用已推送的目标镜像:只有首次构建会推送,后续构建命中复用、不再推送新镜像,构建耗时显著缩短,且目标 tag 的 manifest digest 与首次构建一致。

传参示例(目标与源镜像同仓库;build_config.copy 提供时 enabled 必填):

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="registry.example.com/ns/repo:v1",
                    registry_config=models.CreateTemplateRegistryConfig(
                        auth_config=models.CreateTemplateRegistryAuthConfig(
                            user_name="<username>",
                            password="<password>",
                        ),
                    ),
                ),
            ),
            build_config=models.CreateTemplateBuildConfig(
                copy=models.CreateTemplateCopyAction(
                    enabled=True,
                    image="registry.example.com/ns/repo:v1-fcsandbox",
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'registry.example.com/ns/repo:v1',
        registryConfig: new $FCSandbox20260509.CreateTemplateRegistryConfig({
          authConfig: new $FCSandbox20260509.CreateTemplateRegistryAuthConfig({
            userName: '<username>',
            password: '<password>',
          }),
        }),
      }),
    }),
    buildConfig: new $FCSandbox20260509.CreateTemplateBuildConfig({
      copy: new $FCSandbox20260509.CreateTemplateCopyAction({
        enabled: true,
        image: 'registry.example.com/ns/repo:v1-fcsandbox',
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("registry.example.com/ns/repo:v1"),
                RegistryConfig: &fcsandbox20260509.CreateTemplateRegistryConfig{
                    AuthConfig: &fcsandbox20260509.CreateTemplateRegistryAuthConfig{
                        UserName: tea.String("<username>"),
                        Password: tea.String("<password>"),
                    },
                },
            },
        },
        BuildConfig: &fcsandbox20260509.CreateTemplateBuildConfig{
            Copy: &fcsandbox20260509.CreateTemplateCopyAction{
                Enabled: tea.Bool(true),
                Image:   tea.String("registry.example.com/ns/repo:v1-fcsandbox"),
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("registry.example.com/ns/repo:v1")
                        .setRegistryConfig(new CreateTemplateRegistryConfig()
                                .setAuthConfig(new CreateTemplateRegistryAuthConfig()
                                        .setUserName("<username>")
                                        .setPassword("<password>")))))
        .setBuildConfig(new CreateTemplateBuildConfig()
                .setCopy(new CreateTemplateCopyAction()
                        .setEnabled(true)
                        .setImage("registry.example.com/ns/repo:v1-fcsandbox")));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

查询构建状态

通过 GetTemplate - 查询模板 查询单个模板的配置与构建状态。构建状态通过响应体的 status.state 获取,取值:

状态

说明

pending

已受理,等待构建

building

构建进行中

ready

构建成功,可用于创建沙箱

error

构建失败,status.reason.message 给出具体原因

进入 readyerror 终态后,响应包含 status.finished_at,即构建结束时间。

响应按实际配置回显字段,不使用平台默认值补齐;镜像拉取凭证等敏感字段不会回显。

管理模板

通过 ListTemplates - 查询模板列表 分页查询 Team 下的模板列表,按创建时间倒序返回;通过 DeleteTemplate - 删除模板 删除不再使用的模板:

Python

# 分页查询 Team 下的模板列表。
response = pop_client.list_templates(
    models.ListTemplatesRequest(team_id=team_id)
)
for template in response.body.templates:
    print(template.template_id, template.status.state, template.created_time)
# 还有下一页时,把 response.body.next_token 传入下一次请求的 next_token 继续查询。

# 删除模板。
pop_client.delete_template(template_id, models.DeleteTemplateRequest(team_id=team_id))

TypeScript

// 分页查询 Team 下的模板列表。
const response = await popClient.listTemplates(
  new $FCSandbox20260509.ListTemplatesRequest({ teamID }));
for (const template of response.body.templates ?? []) {
  console.log(template.templateID, template.status.state, template.createdTime);
}
// 还有下一页时,把 response.body.nextToken 传入下一次请求的 nextToken 继续查询。

// 删除模板。
await popClient.deleteTemplate(templateID,
  new $FCSandbox20260509.DeleteTemplateRequest({ teamID }));

Go

// 分页查询 Team 下的模板列表。
resp, err := popClient.ListTemplates(&fcsandbox20260509.ListTemplatesRequest{
    TeamID: tea.String(teamID),
})
if err != nil {
    panic(err)
}
for _, template := range resp.Body.Templates {
    fmt.Println(tea.StringValue(template.TemplateID),
        tea.StringValue(template.Status.State),
        tea.StringValue(template.CreatedTime))
}
// 还有下一页时,把 resp.Body.NextToken 传入下一次请求的 NextToken 继续查询。

// 删除模板。
_, err = popClient.DeleteTemplate(tea.String(templateID), &fcsandbox20260509.DeleteTemplateRequest{
    TeamID: tea.String(teamID),
})
if err != nil {
    panic(err)
}

Java

// 分页查询 Team 下的模板列表。
ListTemplatesResponse response = popClient.listTemplates(
        new ListTemplatesRequest().setTeamID(teamID));
for (PublicTemplate template : response.getBody().getTemplates()) {
    System.out.printf("%s %s %s%n", template.getTemplateID(),
            template.getStatus().getState(), template.getCreatedTime());
}
// 还有下一页时,把 response.getBody().getNextToken() 传入下一次请求的 nextToken 继续查询。

// 删除模板。
popClient.deleteTemplate(templateID,
        new DeleteTemplateRequest().setTeamID(teamID));
  • 模板处于任意状态(含构建中)都可删除,删除会终止进行中的构建。

  • 删除成功后模板名立即释放,可创建同名新模板。

  • 平台内置模板(如 base)不允许删除。

  • 刚创建的模板可能延迟数秒才会出现在列表中,属于索引同步的正常现象,GetTemplate 不受影响。

使用限制

  • 所有模板接口都必须传入 Team ID,缺失时返回 400 InvalidParameter

  • 请求体采用严格 JSON 校验:未知字段、重复 key、显式 null 都会返回 400;请求体上限 1 MiB。

  • 同一 Team 中模板名称不能重复,重复创建返回 409 TemplateAlreadyExists

  • 没有独立的更新接口,更新模板需要先删除旧模板再重新创建。

通过 E2B SDK 本地构建

E2B SDK 在本地定义模板并提交构建,适合首次验证和个人开发。安装依赖:

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

创建 .env 文件:

# 使用前请替换为自己的 API Key
E2B_API_KEY=e2b_xxx
FROM_IMAGE="fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.49"

# 北京生产环境地址示例值
E2B_API_URL=https://api.cn-beijing.e2b.fc.aliyuncs.com
E2B_DOMAIN=cn-beijing.e2b.fc.aliyuncs.com

FROM_IMAGE 默认为云沙箱官方镜像,无需凭证与网络配置;其他官方镜像地址参见上文镜像源。使用需要鉴权或网络配置的私有镜像源时,请改用本文 OpenAPI 方式创建模板。

运行脚本,将镜像构建为模板,并创建沙箱验证:

#!/usr/bin/env python3

import os
import time

from dotenv import load_dotenv
from e2b import Template, default_build_logger
from e2b_code_interpreter import Sandbox


load_dotenv()

FROM_IMAGE = os.getenv("FROM_IMAGE", "").strip()
RUN_CODE = "print('hello')"

# SDK 自动读取 E2B_API_KEY / E2B_API_URL / E2B_DOMAIN 环境变量。
build = Template.build(
    Template().from_image(FROM_IMAGE),
    name=f"template-{int(time.time())}",
    cpu_count=2,
    memory_mb=2048,
    on_build_logs=default_build_logger(),
)
sandbox = Sandbox.create(template=build.template_id)

try:
    print(f"template_id: {build.template_id}")
    print(f"build_id: {build.build_id}")
    print(f"sandbox_id: {sandbox.sandbox_id}")
    print(f"sandbox_domain: {sandbox.sandbox_domain}")
    print(f"envd_api_url: {sandbox.envd_api_url}")

    execution = sandbox.run_code(RUN_CODE)
    stdout = "".join(execution.logs.stdout or [])
    stderr = "".join(execution.logs.stderr or [])

    print(f"run_code code: {RUN_CODE}")
    print(f"run_code stdout: {stdout.strip()}")
    print(f"run_code stderr: {stderr.strip()}")
    print(f"run_code error: {execution.error}")

    if execution.error is not None:
        raise RuntimeError(f"run_code 执行失败: {execution.error}")
    if stdout.strip() != "hello":
        raise RuntimeError(f"run_code stdout 不符合预期: {stdout!r}")
finally:
    print(f"killing sandbox: {sandbox.sandbox_id}")
    sandbox.kill()
    print("sandbox killed")

使用 CLI 查看模板并创建沙箱

如果已经安装 E2B CLI,可以通过模板列表确认构建状态:

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

e2b template list

模板状态为 ready 后,可以使用模板名称或 template_id 创建沙箱。模板名称可以是上一步本地构建产出的 template-{时间戳}my-code-interpreter-v1 为示例名:

e2b sandbox create my-code-interpreter-v1

进入沙箱后建议先验证关键依赖:

env | sort | head
python3 --version
which python3

模板名称建议

建议在模板名称中包含业务名、基础镜像、关键依赖版本或日期,例如 agent-python313-20260531。生产环境避免覆盖正在使用的模板,先创建新模板并灰度验证。

E2B SDK 本地构建适合使用官方镜像快速验证。构建拉取网络、运行时 VPC 等配置需要在创建模板时设置,请改用本文 OpenAPI 方式创建模板,使用 registry_config.network_configvpc_config 字段,参见上文镜像源运行时网络

常见问题

排查接口问题时,优先查看 SDK 异常对象中的 codemessagerequest_id;联系技术支持时请提供 request_id

返回 InvalidParameter 错误码

检查 Team ID 是否缺失,以及请求体是否包含未知字段、重复 key、显式 null,或传入了暂不支持的 start_commandready_command 字段。

返回 TemplateAlreadyExists 错误码

同一 Team 中已存在同名模板。可通过 ListTemplates 查询确认,不再使用后通过 DeleteTemplate 删除,或更换模板名称。

返回 TemplateNotFound 错误码

模板不存在、已删除,或不属于当前 Team。

返回 SignatureDoesNotMatch 错误

签名校验失败。常见原因为 AccessKey Secret 不正确、STS 临时凭证过期或本机时间偏差过大;使用官方 SDK 发起调用可避免手工构造签名带来的问题。

status.stateerror

构建失败,查看 status.reason.message 中的具体原因,常见为镜像拉取失败、镜像不满足要求或 VPC 网络配置错误。镜像源的约束与传参写法参见上文“镜像源”,vSwitch 可用区问题参见下文vSwitch 所在可用区不支持

模板构建慢

优先检查自己的 ACR EE 镜像体积、网络链路、镜像层缓存和 ACR EE 私网访问配置。基础镜像越大,首次构建通常越慢。

如果只是排查模板构建链路是否正常,可以临时改用云沙箱官方镜像做对照验证。官方镜像已包含云沙箱运行依赖,通常更容易排除镜像适配问题。

vSwitch 所在可用区不支持

如果构建或运行过程中遇到 vSwitch is in unsupported zone,说明当前选择的 vSwitch 所在可用区不被函数计算支持。

处理方式:

  1. 根据错误信息确认当前 vSwitch 所在可用区。

  2. 从函数计算支持的可用区中选择一个目标可用区。

  3. 在同一个 VPC 中新建该可用区下的 vSwitch。

  4. 使用这个新的 vSwitch 配置函数计算或相关网络配置。

  5. 重新构建模板并创建沙箱验证。

同一个 VPC 内不同可用区的 vSwitch 默认内网互通。因此,即使业务资源在其他可用区,也可以在同一个 VPC 中新增一个函数计算支持可用区下的 vSwitch,用于完成函数计算侧网络接入。

沙箱创建成功但 run_code 失败

run_code 会调用镜像的 python3 命令执行代码,如果镜像不包含 python3 或相关依赖,可能会导致执行失败。建议先确认镜像是否包含代码解释器所需依赖。可以在沙箱中运行:

python3 --version
which python3
env | sort | head -50

沙箱启动慢

如果沙箱启动较慢,可以先使用以下脚本将镜像压缩为单层镜像,再使用原镜像和压缩后的镜像进行 A/B 对照。测试时应保持模板规格、地域、并发量和镜像启动命令一致,并分别记录模板构建、沙箱创建和首次执行耗时。

脚本依赖 Docker 和 Python 3。运行前请确认源镜像已拉取到本地,并为目标镜像使用新的 tag。使用压缩后的镜像前请先完成验证。

#!/usr/bin/env bash
set -euo pipefail

PLATFORM="linux/amd64"

SRC="${1:?用法:$0 <源镜像> [目标 tag]}"

if [ "$#" -ge 2 ]; then
    DST="$2"
elif [[ "$SRC" == *@* ]] || [[ "${SRC##*/}" != *:* ]]; then
    echo "错误:源镜像使用 digest 或未指定 tag 时,请显式传入目标 tag。" >&2
    exit 2
else
    DST="${SRC}-flat"
fi

echo "==> 源镜像:   $SRC"
echo "==> 目标镜像: $DST"
echo "==> 平台:     $PLATFORM"

echo "==> 提取镜像元数据..."
RAW_CMD=$(docker inspect --format='{{json .Config.Cmd}}' "$SRC")
RAW_ENTRYPOINT=$(docker inspect --format='{{json .Config.Entrypoint}}' "$SRC")
RAW_ENV=$(docker inspect --format='{{json .Config.Env}}' "$SRC")
RAW_EXPOSE=$(docker inspect --format='{{json .Config.ExposedPorts}}' "$SRC")
RAW_WORKDIR=$(docker inspect --format='{{.Config.WorkingDir}}' "$SRC")
RAW_USER=$(docker inspect --format='{{.Config.User}}' "$SRC")
RAW_VOLUMES=$(docker inspect --format='{{json .Config.Volumes}}' "$SRC")
RAW_LABELS=$(docker inspect --format='{{json .Config.Labels}}' "$SRC")
RAW_STOPSIGNAL=$(docker inspect --format='{{.Config.StopSignal}}' "$SRC")

CHANGES=()

if [ "$RAW_CMD" != "null" ] && [ -n "$RAW_CMD" ]; then
    CHANGES+=(--change "CMD $RAW_CMD")
fi

if [ "$RAW_ENTRYPOINT" != "null" ] && [ -n "$RAW_ENTRYPOINT" ]; then
    CHANGES+=(--change "ENTRYPOINT $RAW_ENTRYPOINT")
fi

if [ "$RAW_ENV" != "null" ] && [ "$RAW_ENV" != "[]" ] && [ -n "$RAW_ENV" ]; then
    while IFS= read -r env_entry; do
        CHANGES+=(--change "ENV ${env_entry%%=*}=\"${env_entry#*=}\"")
    done < <(echo "$RAW_ENV" | python3 -c '
import json
import sys
for entry in json.load(sys.stdin):
    print(entry)
')
fi

if [ "$RAW_EXPOSE" != "null" ] && [ "$RAW_EXPOSE" != "{}" ] && [ -n "$RAW_EXPOSE" ]; then
    while IFS= read -r port; do
        CHANGES+=(--change "EXPOSE $port")
    done < <(echo "$RAW_EXPOSE" | python3 -c '
import json
import sys
for key in json.load(sys.stdin):
    print(key)
')
fi

if [ -n "$RAW_WORKDIR" ]; then
    CHANGES+=(--change "WORKDIR $RAW_WORKDIR")
fi

if [ -n "$RAW_USER" ]; then
    CHANGES+=(--change "USER $RAW_USER")
fi

if [ "$RAW_VOLUMES" != "null" ] && [ "$RAW_VOLUMES" != "{}" ] && [ -n "$RAW_VOLUMES" ]; then
    while IFS= read -r volume; do
        CHANGES+=(--change "VOLUME $volume")
    done < <(echo "$RAW_VOLUMES" | python3 -c '
import json
import sys
for key in json.load(sys.stdin):
    print(key)
')
fi

if [ "$RAW_LABELS" != "null" ] && [ "$RAW_LABELS" != "{}" ] && [ -n "$RAW_LABELS" ]; then
    while IFS= read -r label; do
        CHANGES+=(--change "LABEL $label")
    done < <(echo "$RAW_LABELS" | python3 -c '
import json
import sys
for key, value in json.load(sys.stdin).items():
    print(f"{key}=\"{value}\"")
')
fi

if [ -n "$RAW_STOPSIGNAL" ]; then
    CHANGES+=(--change "STOPSIGNAL $RAW_STOPSIGNAL")
fi

ORIG_LAYERS=$(docker inspect --format='{{len .RootFS.Layers}}' "$SRC")
echo "==> 原始镜像层数:$ORIG_LAYERS"

echo "==> 开始压缩镜像层..."
CONTAINER_ID=$(docker create --platform "$PLATFORM" "$SRC" /bin/true)
trap 'docker rm -f "${CONTAINER_ID:-}" >/dev/null 2>&1 || true' EXIT

docker export "$CONTAINER_ID" | docker import \
    --platform "$PLATFORM" \
    "${CHANGES[@]}" \
    - "$DST"

NEW_LAYERS=$(docker inspect --format='{{len .RootFS.Layers}}' "$DST")
ORIG_SIZE=$(docker inspect --format='{{.Size}}' "$SRC" | awk '{printf "%.0f MB", $1/1024/1024}')
NEW_SIZE=$(docker inspect --format='{{.Size}}' "$DST" | awk '{printf "%.0f MB", $1/1024/1024}')
ORIG_ARCH=$(docker inspect --format='{{.Architecture}}' "$SRC")
NEW_ARCH=$(docker inspect --format='{{.Architecture}}' "$DST")

echo ""
echo "==> 完成"
echo "    原始镜像:$ORIG_LAYERS 层,$ORIG_SIZE,架构=$ORIG_ARCH"
echo "    压缩镜像:$NEW_LAYERS 层,$NEW_SIZE,架构=$NEW_ARCH"
echo "    Tag:$DST"
使用示例

将上述脚本保存为 flatten-image.sh 并赋予执行权限。以下示例将源镜像压缩为单层镜像,并生成新的目标 tag:

chmod +x flatten-image.sh
./flatten-image.sh \
  registry.example.com/runtime/python:3.12-v1 \
  registry.example.com/runtime/python:3.12-v1-flat

脚本执行完成后,确认输出的镜像层数和大小符合预期,再将目标镜像推送到镜像仓库,并在构建模板时使用目标镜像地址:

docker push registry.example.com/runtime/python:3.12-v1-flat

如果压缩后的镜像在相同条件下启动明显更快,建议在后续构建镜像时合并构建步骤,减少镜像层数。