全部產品
Search
文件中心

Function Compute:構建和系統管理範本

更新時間:Oct 15, 2026

雲沙箱模板支援通過阿里雲 OpenAPI 建立、查詢和刪除。CreateTemplate 一次完成模板建立和首次構建,構建非同步執行,輪詢 GetTemplate 擷取構建狀態;模板就緒後即可用於建立沙箱,參見快速開始。本文介紹如何安裝 SDK、配置許可權、建立模板並構建,以及如何配置鏡像源、運行時網路和日誌,協助您以自動化方式構建和管理沙箱模板。

構建自訂鏡像模板有兩條路徑,鏡像要求相同,網路設定能力不同:通過 OpenAPI 構建,調用 CreateTemplate 由服務端完成構建,適合自動化指令碼、基礎設施即代碼(IaC)和平台化整合情境;通過 E2B SDK 本地構建,在本地運行 Template.build 提交構建,適合首次驗證和個人開發。

前提條件

使用前需要準備:

  • 已開通Function Compute和雲沙箱。

  • 已在雲沙箱控制台建立 Team,並擷取 Team ID。

  • 準備阿里雲帳號或 RAM 使用者,並為調用身份配置模板 API 許可權,參見下文許可權配置。

  • 確認模板所在地區,例如北京地區為 cn-beijing,全部地區參見支援地區。

  • 使用 E2B SDK 本地構建時,擷取串連參數 E2B_API_KEY、E2B_API_URL 和 E2B_DOMAIN,SDK 會自動讀取這三個環境變數。參數詳情見 E2B SDK 接入參數說明。

Team 的建立和 Team ID 擷取方式參見建立 Team。

許可權配置

調用模板 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 包含 name、team_id、runtime_config、build_config 四個欄位,模板建立後立即開始首次構建:

  • runtime_config:最終模板的運行規格。cpu(核心數)、memory_size(MB)、disk_size(MB)按需填寫,預設時使用預設規格;sandbox_config.image 為必填的構建源鏡像。

  • build_config:構建方式。整段預設時按源鏡像自動推導——雲沙箱官方鏡像直接使用,不再構建;其他鏡像自動執行 copy(處理並推送目標鏡像)和 envdInject(注入沙箱運行依賴)。

鏡像要求與各鏡像源的約束和傳參寫法參見下文鏡像要求與鏡像源。

以下樣本以北京地區為例,使用雲沙箱官方鏡像建立模板,無需自備 ACR EE(Container Registry ACR 企業版)執行個體,輪詢構建狀態至就緒後刪除模板:

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.52"

# 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, generation=1),
            ),
        )
    )
)
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.52';

  // 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, generation: 1 }),
      }),
    }),
  }));
  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.52"

    // 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),
                    Generation: tea.Int32(1),
                },
            },
        },
    })
    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.52";

        // 1. 建立模板並提交首次構建,返回 templateID。
        CreateTemplateInput body = new CreateTemplateInput()
                .setName("pop-demo-" + System.currentTimeMillis() / 1000)
                .setTeamID(teamID)
                .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                        .setSandboxConfig(new CreateTemplateSandboxConfig()
                                .setImage(fromImage)
                                .setGeneration(1)));
        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);
    }
}

將上述範例程式碼分別儲存為 create_template.py、create_template.ts、main.go、src/main/java/CreateTemplateDemo.java 後,運行樣本:

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"

構建為非同步執行,輪詢耗時取決於鏡像大小,通常在數十秒到數分鐘之間。

使用第二代運行時

雲沙箱運行時分為兩代。第二代基於輕量虛擬機器(MicroVM)隔離,提供快照等第一代不具備的能力(快照僅第二代運行時可用,參見快照(Snapshot)(邀測))。

第二代運行時當前僅對已開通白名單的帳號和地區可用,未開通時模板構建會以 MicroSandbox runtime is not enabled for this account 失敗。如需使用,請聯絡支援人員開通。

在 runtime_config.sandbox_config 中傳 generation 選擇代際:1 為第一代,2 為第二代。

第二代與第一代的差異:

維度

第一代

第二代

os_type

不支援,提供即 400 InvalidParameter

可選枚舉:linux-amd64(預設)、android-36-osworld、windows-osworld,不區分大小寫

磁碟可用空間

配置的磁碟大小即為可寫空間,不包含鏡像自身佔用

配置的磁碟大小包含鏡像自身佔用,實際可寫空間 = 磁碟大小 - 鏡像大小

鏡像發行版

推薦 Ubuntu 20.04、Debian 12 及以上,不強制

僅支援 Ubuntu/Debian 系,且需保留 systemd、openssh-server、sudo、chrony、fuse3、iptables 等系統組件(或保證 apt-get 可用);Alpine、BusyBox 等精簡 rootfs 不可用

/workspace 工作目錄

構建時由平台注入

不注入,需要時請在鏡像中內建

啟動與就緒命令(start_command/ready_command)

不支援,提供即 400 InvalidParameter

可選,用於把常駐進程固化到模板,參見啟動與就緒命令

傳參樣本(用 ACR EE 內網鏡像建立第二代模板,並通過 start_command/ready_command 把常駐 HTTP 服務固化到模板;輪詢與刪除步驟與上文相同):

Python

response = pop_client.create_template(
    models.CreateTemplateRequest(
        body=models.CreateTemplateInput(
            name=f"pop-demo-gen2-{int(time.time())}",
            team_id=team_id,
            runtime_config=models.CreateTemplateRuntimeConfig(
                cpu=2,
                memory_size=2048,
                disk_size=15360,
                sandbox_config=models.CreateTemplateSandboxConfig(
                    image="test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1",
                    # 使用第二代運行時。
                    generation=2,
                    # 啟動命令在構建期執行並就緒後固化到模板快照。
                    start_command="python3 -m http.server 8000",
                    # 就緒命令退出碼為 0 一次即判定就緒。
                    ready_command="curl -sf http://localhost:8000/ > /dev/null",
                ),
            ),
        )
    )
)

TypeScript

const created = await popClient.createTemplate(new $FCSandbox20260509.CreateTemplateRequest({
  body: new $FCSandbox20260509.CreateTemplateInput({
    name: `pop-demo-gen2-${Math.floor(Date.now() / 1000)}`,
    teamID,
    runtimeConfig: new $FCSandbox20260509.CreateTemplateRuntimeConfig({
      cpu: 2,
      memorySize: 2048,
      diskSize: 15360,
      sandboxConfig: new $FCSandbox20260509.CreateTemplateSandboxConfig({
        image: 'test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1',
        // 使用第二代運行時。
        generation: 2,
        // 啟動命令在構建期執行並就緒後固化到模板快照。
        startCommand: 'python3 -m http.server 8000',
        // 就緒命令退出碼為 0 一次即判定就緒。
        readyCommand: 'curl -sf http://localhost:8000/ > /dev/null',
      }),
    }),
  }),
}));

Go

created, err := popClient.CreateTemplate(&fcsandbox20260509.CreateTemplateRequest{
    Body: &fcsandbox20260509.CreateTemplateInput{
        Name:   tea.String(fmt.Sprintf("pop-demo-gen2-%d", time.Now().Unix())),
        TeamID: tea.String(teamID),
        RuntimeConfig: &fcsandbox20260509.CreateTemplateRuntimeConfig{
            Cpu:        tea.Float32(2),
            MemorySize: tea.Int32(2048),
            DiskSize:   tea.Int32(15360),
            SandboxConfig: &fcsandbox20260509.CreateTemplateSandboxConfig{
                Image: tea.String("test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1"),
                // 使用第二代運行時。
                Generation: tea.Int32(2),
                // 啟動命令在構建期執行並就緒後固化到模板快照。
                StartCommand: tea.String("python3 -m http.server 8000"),
                // 就緒命令退出碼為 0 一次即判定就緒。
                ReadyCommand: tea.String("curl -sf http://localhost:8000/ > /dev/null"),
            },
        },
    },
})
if err != nil {
    panic(err)
}

Java

CreateTemplateInput body = new CreateTemplateInput()
        .setName("pop-demo-gen2-" + System.currentTimeMillis() / 1000)
        .setTeamID(teamID)
        .setRuntimeConfig(new CreateTemplateRuntimeConfig()
                .setCpu(2f)
                .setMemorySize(2048)
                .setDiskSize(15360)
                .setSandboxConfig(new CreateTemplateSandboxConfig()
                        .setImage("test-registry-vpc.cn-beijing.cr.aliyuncs.com/runtime/python:3.12-v1")
                        // 使用第二代運行時。
                        .setGeneration(2)
                        // 啟動命令在構建期執行並就緒後固化到模板快照。
                        .setStartCommand("python3 -m http.server 8000")
                        // 就緒命令退出碼為 0 一次即判定就緒。
                        .setReadyCommand("curl -sf http://localhost:8000/ > /dev/null")));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

啟動命令和就緒命令的語義、限制與 E2B SDK 用法參見啟動與就緒命令。GetTemplate 僅在建立時提供非空 os_type 才回顯 start_command/ready_command,未回顯不代表未生效。

鏡像要求

推薦基礎鏡像:Ubuntu 20.04、Debian 12(bookworm)及以上。第一代運行時不強制發行版,第二代運行時僅支援 Ubuntu/Debian 系。自訂鏡像需滿足下表要求,按“硬性 / 條件 / 推薦”分級:

層級

要求

不滿足時影響

硬性

架構為 linux/amd64;若為多架構鏡像,其 manifest 列表必須包含 linux/amd64 變體

模板構建直接失敗

硬性

ACR 鏡像不得開啟鏡像加速選項

模板構建直接失敗

條件

鏡像中存在固定路徑 /bin/bash(不僅是 PATH 中可找到 Bash)

commands.run、PTY 失敗

硬性

/etc/passwd、/etc/group 為標準檔案且可寫

沙箱無法初始化預設使用者,容器啟動失敗

條件

PATH 中存在 python3 或 python

Python run_code 不可用

條件

PATH 中存在 node

JavaScript run_code 不可用

條件

按需提供:git、openssh-client、CA 憑證、編譯工具鏈

對應的 Git、SSH、HTTPS、編譯安裝操作不可用

推薦

系統 PATH 至少包含 /usr/local/bin、/usr/bin、/bin

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

向自己的鏡像倉庫推送鏡像時,不要向同一個 tag 推送內容不同的鏡像。每個不同的鏡像都必須使用新的唯一 tag,例如使用版本號碼、日期或提交 ID,並讓模板固定引用對應 tag。

鏡像源

構建源鏡像分為四類:官方鏡像、ACR 企業版、公網 OCI(Open Container Initiative,開放容器倡議)倉庫、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.52
fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/code-interpreter-v1:v0.0.52

約束:

  • 只能引用發行的官方 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.52",
                    generation=1,
                ),
            ),
        )
    )
)

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.52',
        generation: 1,
      }),
    }),
  }),
}));

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.52"),
                Generation: tea.Int32(1),
            },
        },
    },
})
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.52")
                        .setGeneration(1)));
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 執行個體的存取控制中已添加至少一個Virtual Private Cloud 綁定,且綁定處於“已生效”狀態。雲沙箱經該綁定在 VPC 內分配的訪問 IP 拉取鏡像;綁定剛建立時存在“生效中”視窗,綁定失效會導致拉取失敗,可在執行個體的存取控制頁面確認綁定狀態與訪問 IP。

  • 拉取網路的 VPC 需為 ACR EE 執行個體已綁定的 VPC,且該 VPC 下至少有一個 vSwitch(交換器)位於Function Compute支援的可用性區域。

  • ACR EE 鏡像倉庫、VPC、vSwitch 與雲沙箱需位於同一地區,例如本文預設使用北京地區。

  • VPC 網段需使用 RFC 1918 規定的私人位址區段,即 10.0.0.0/8、172.16.0.0/12 或 192.168.0.0/16。不支援公網私用,詳情請參見VPC 常見問題。

  • VPC 下需至少配置一個自建安全性群組,其出方向需允許存取 443/TCP。普通安全性群組出方向預設允許所有訪問(無出方向規則即滿足);企業級安全性群組出方向預設禁止所有訪問,需顯式添加允許存取 443/TCP 的出方向規則。

  • 需確保 ACR EE 執行個體在 VPC 記憶體在入口 ENI(Elastic Network Interface,彈性網卡)。如果 ENI 已被刪除,可以通過重新綁定 VPC 的方式重新建立。

Function Compute支援的可用性區域會隨地區和產品能力調整,最新列表請參考配置網路。

常見失敗:

  • 鏡像拉取失敗:依次檢查鏡像地址、地區、命名空間、倉庫許可權、VPC 綁定和存取控制配置。使用私人 ACR EE 鏡像時,網路設定錯誤比 SDK 參數錯誤更常見。

  • 拉取網路 vSwitch 可用性區域不支援:處理方式參見下文常見問題vSwitch 所在可用性區域不支援。

拉取方式:只傳 image,平台按鏡像地址推導 ACR EE 執行個體與倉庫類型,使用執行個體臨時授權拉取,並從執行個體綁定的 VPC 推導拉取網路。需要顯式指定執行個體、憑證或拉取網路時,再傳 acr_instance_id、registry_config.auth_config 與 registry_config.network_config;執行個體推匯出的 vSwitch 落在不支援的可用性區域導致拉取失敗時,必須用 network_config 顯式指定 VPC、vSwitch 和安全性群組三項配置。

傳參樣本:

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",
                    generation=1,
                    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',
        generation: 1,
        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"),
                Generation:     tea.Int32(1),
                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")
                        .setGeneration(1)
                        .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 與認證配置。其中“公網可達”指Function Compute能夠從對應地區訪問到該倉庫,而不只是倉庫對公網開放了網域名稱。

約束:

  • 只需 auth_config 憑證兩項(使用者名稱/密碼),無需 network_config;憑證需要對該倉庫有拉取許可權。

  • build_config 預設時構建產物會推送回同一倉庫的新 tag,因此憑證還需要具備推送許可權。

  • network_config、cert_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",
                    generation=1,
                    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',
        generation: 1,
        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"),
                Generation:     tea.Int32(1),
                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")
                        .setGeneration(1)
                        .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",
                    generation=1,
                    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',
        generation: 1,
        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"),
                Generation:     tea.Int32(1),
                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")
                        .setGeneration(1)
                        .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.52",
                    generation=1,
                ),
            ),
        )
    )
)

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.52',
        generation: 1,
      }),
    }),
  }),
}));

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.52"),
                Generation: tea.Int32(1),
            },
        },
    },
})
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.52")
                        .setGeneration(1)));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

日誌配置

沙箱作業記錄可通過 runtime_config 中的 log_config 投遞到您帳號下的Log Service SLS,僅開放 project 與 logstore 兩項,需與雲沙箱位於同一地區:

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.52",
                    generation=1,
                ),
            ),
        )
    )
)

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.52',
        generation: 1,
      }),
    }),
  }),
}));

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.52"),
                Generation: tea.Int32(1),
            },
        },
    },
})
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.52")
                        .setGeneration(1)));
String templateID = popClient
        .createTemplate(new CreateTemplateRequest().setBody(body))
        .getBody().getTemplateID();

目標鏡像與構建複用

構建非官方鏡像時,平台會臨時啟動一個Function Compute函數,由它拉取源鏡像、加入雲沙箱運行所需的依賴,並推送為目標鏡像;模板最終使用這個目標鏡像建立沙箱運行環境。該臨時函數建立在您自己的帳號下,構建期間會產生少量Function Compute費用。

目標鏡像可以通過 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",
                    generation=1,
                    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',
        generation: 1,
        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"),
                Generation:     tea.Int32(1),
                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")
                        .setGeneration(1)
                        .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 給出具體原因

進入 ready 或 error 終態後,響應包含 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.52"

# 北京生產環境地址樣本值
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 建立沙箱。模板需先建立並構建完成,以下命令中的 my-code-interpreter-v1 為樣本模板名,請替換為實際模板名,例如上一步本地構建產出的 template-{時間戳記}:

e2b sandbox create my-code-interpreter-v1

進入沙箱後建議先驗證關鍵依賴:

env | sort | head
python3 --version
which python3

模板名稱建議

建議在模板名稱中包含業務名、基礎鏡像、關鍵依賴版本或日期,例如 agent-python313-20260531。生產環境避免覆蓋正在使用的模板,先建立新模板並灰階驗證。

E2B SDK 本地構建適合使用官方鏡像快速驗證。構建拉取網路、運行時 VPC 等配置需要在建立模板時設定,請改用本文 OpenAPI 方式建立模板,使用 registry_config.network_config、vpc_config 欄位,參見上文鏡像源與運行時網路。

常見問題

排查介面問題時,優先查看 SDK 異常對象中的 code、message 和 request_id;聯絡支援人員時請提供 request_id。

返回 InvalidParameter 錯誤碼

檢查 Team ID 是否缺失,以及請求體是否包含未知欄位、重複 key、顯式 null,或傳入了僅第二代運行時支援的 start_command、ready_command 欄位。

返回 TemplateAlreadyExists 錯誤碼

同一 Team 中已存在同名模板。可通過 ListTemplates 查詢確認,不再使用後通過 DeleteTemplate 刪除,或更換模板名稱。

返回 TemplateNotFound 錯誤碼

模板不存在、已刪除,或不屬於當前 Team。

返回 SignatureDoesNotMatch 錯誤

簽名校正失敗。常見原因為 AccessKey Secret 不正確、STS 臨時憑證到期或本機時間偏差過大;使用官方 SDK 發起調用可避免手工建構簽章帶來的問題。

status.state 為 error

構建失敗,查看 status.reason.message 中的具體原因,常見為鏡像拉取失敗、鏡像不滿足要求或 VPC 網路設定錯誤。鏡像源的約束與傳參寫法參見上文鏡像源,vSwitch 可用性區域問題參見下文vSwitch 所在可用性區域不支援。

模板構建慢

優先檢查自己的 ACR EE 鏡像體積、網路鏈路、鏡像層緩衝和 ACR EE 私網訪問配置。基礎鏡像越大,首次構建通常越慢。

如果只是排查模板構建鏈路是否正常,可以臨時使用雲沙箱官方鏡像等已有 E2B 相容鏡像做對照驗證。這類鏡像已經包含雲沙箱運行依賴,通常更容易排除鏡像適配問題。

vSwitch 所在可用性區域不支援

如果構建或運行過程中遇到 vSwitch is in unsupported zone,說明當前選擇的 vSwitch 所在可用性區域不被Function Compute支援。

處理方式:

  1. 根據錯誤資訊確認當前 vSwitch 所在可用性區域。

  2. 在Function Compute支援的可用性區域中選擇一個目標可用性區域(Zone)。

  3. 在同一個 VPC 中建立該 Zone 下的 vSwitch。

  4. 使用這個新的 vSwitch 更新Function Compute側的網路設定。

  5. 重新構建模板並建立沙箱驗證。

同一個 VPC 內不同可用性區域的 vSwitch 預設內網互連。因此,即使業務資源在其他可用性區域,也可以在同一個 VPC 中新增一個Function Compute支援可用性區域下的 vSwitch,用於完成Function Compute側網路接入。

沙箱建立成功但 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

如果壓縮後的鏡像在相同條件下啟動明顯更快,建議在後續構建鏡像時合并構建步驟,減少鏡像層數。