使用 GHCR 自定义镜像构建 Sandbox 模板

更新时间:
复制 MD 格式

云沙箱支持直接从容器镜像仓库拉取镜像来构建 Sandbox 模板。当业务需要预置特定语言运行时、系统依赖、私有 SDK 或数据科学工具链时,把这些内容打包成一个容器镜像,再从镜像构建模板,比在运行时逐条安装依赖更可控、更可复现。

本文以 GitHub Container Registry(GHCR) 上的私有镜像为例,说明如何在美国(硅谷)地域(us-west-1)从 ghcr.io/<owner>/<image>:<tag> 构建模板、创建 Sandbox 并校验运行结果。GHCR 属于需要鉴权的私有仓库,因此构建时需要携带拉取凭据。

业务场景

  • 团队把统一的 Python/Node 运行时和常用依赖打包成镜像,多个 Agent 共用同一基础模板。

  • 分析类任务需要预装 pandasnumpy、绘图库等重依赖,希望构建期一次装好,而不是每次运行时安装。

  • 私有 SDK 或内部工具只发布到 GHCR 私有仓库,不能公开拉取。

  • 需要把镜像版本与模板版本对齐,做到构建可追溯、可回滚。

前置条件

  • 已开通云沙箱,并获取目标地域的 E2B_API_KEY

  • 目标地域为美国(硅谷),对应接入点:

    • api_urlhttps://api.us-west-1.e2b.fc.aliyuncs.com

    • domainus-west-1.e2b.fc.aliyuncs.com

  • 已在 GHCR 上准备好镜像,例如 ghcr.io/<owner>/python:3.10

  • 已创建用于拉取镜像的 GitHub 凭据(见下节「镜像仓库凭据」)。

  • 安装 e2b SDK:

pip install e2b

镜像仓库凭据

从 GHCR 私有仓库拉取镜像需要携带用户名和密码。这里的凭据是 GitHub 的 Personal Access Token (classic),不是账号登录密码:

  • 用户名:填 GitHub 用户名(镜像所属的 owner)。

  • 密码:填创建的 Personal Access Token (classic) 值(形如 ghp_xxxxxxxx)。

Token 权限要求:创建 Personal Access Token (classic) 时,勾选 read:packageswrite:packages 两项 packages 权限。read:packages 用于拉取镜像(构建阶段必需),勾选 write:packages 时 GitHub 会自动包含 read:packages。缺少这些权限会导致构建阶段拉取镜像失败(鉴权 401 / 403,可见 GitHub 相关文档)。

创建路径:GitHub → SettingsDeveloper settingsPersonal access tokensTokens (classic)Generate new token (classic),在 Select scopes 中勾选 read:packageswrite:packages

构建请求通过以下两个 HTTP Header 传递凭据,SDK 侧以 Template.build()headers 参数注入:

Header含义取值
X-E2B-Template-Source-Username镜像仓库用户名GitHub 用户名
X-E2B-Template-Source-Password镜像仓库密码Personal Access Token (classic)

凭据属于敏感信息,建议通过环境变量或密钥管理服务注入,不要硬编码进代码或提交进代码仓库。

推荐流程

建议把「自定义镜像 → 模板 → Sandbox」拆成四步:

  1. Template().from_image(<镜像地址>) 声明镜像来源。

  2. 调用 Template.build 构建模板,通过 headers 参数传入仓库凭据,并指定 cpu_countmemory_mb 等规格,拿到 template_id

  3. template_id 创建 Sandbox,等待数据面就绪后执行命令做冒烟校验。

  4. 校验通过后销毁 Sandbox,避免资源长期占用。

其中模板构建是一次性动作:同一镜像构建出的 template_id 可以被后续多次 Sandbox.create 复用,生产上通常把构建和运行拆成两个阶段,而不是每次运行都重新构建。

示例代码

以下示例从 GHCR 私有镜像 ghcr.io/<owner>/python:3.10 构建模板,创建 Sandbox,执行一段 Python 打印校验标记,最后销毁 Sandbox。用户名、密码、E2B_API_KEY 均从环境变量读取。

import os
import time
import uuid

from e2b import Sandbox, Template, default_build_logger

API_URL = "https://api.us-west-1.e2b.fc.aliyuncs.com"
DOMAIN = "us-west-1.e2b.fc.aliyuncs.com"
SOURCE_IMAGE = "ghcr.io/<owner>/python:3.10"
SANDBOX_MARKER = "sandbox-ok"
READY_TIMEOUT_SECONDS = 90
READY_INTERVAL_SECONDS = 3


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


def main() -> int:
    # USERNAME=GitHub 用户名;PASSWORD=Personal Access Token (classic)
    username = required_env("USERNAME")
    password = required_env("PASSWORD")
    api_key = required_env("E2B_API_KEY")

    name = f"custom-python-{int(time.time())}-{uuid.uuid4().hex[:8]}"
    headers = {
        "X-E2B-Template-Source-Username": username,
        "X-E2B-Template-Source-Password": password,
    }

    # 1. 从 GHCR 私有镜像构建模板
    build = Template.build(
        Template().from_image(SOURCE_IMAGE),
        name=name,
        cpu_count=2,
        memory_mb=2048,
        skip_cache=False,
        on_build_logs=default_build_logger(),
        headers=headers,
        api_key=api_key,
        api_url=API_URL,
        domain=DOMAIN,
    )
    print(f"template_id={build.template_id}")

    # 2. 用 template_id 创建 Sandbox
    sandbox = Sandbox.create(
        template=build.template_id,
        timeout=900,
        api_key=api_key,
        api_url=API_URL,
        domain=DOMAIN,
    )
    print(f"sandbox_id={sandbox.sandbox_id}")

    try:
        # 3. 等待数据面就绪并执行冒烟校验
        last_error = None
        deadline = time.monotonic() + READY_TIMEOUT_SECONDS
        while time.monotonic() < deadline:
            try:
                result = sandbox.commands.run(
                    f"python3 - <<'PY'\nprint('{SANDBOX_MARKER}')\nPY",
                    timeout=60,
                )
                break
            except Exception as err:
                last_error = err
                time.sleep(READY_INTERVAL_SECONDS)
        else:
            raise RuntimeError(f"数据面等待超时: {last_error}") from last_error

        if result.exit_code != 0 or SANDBOX_MARKER not in result.stdout:
            raise RuntimeError(f"校验失败: exit={result.exit_code}, stdout={result.stdout}")
        print(f"stdout={result.stdout.strip()}")
    finally:
        # 4. 销毁 Sandbox
        sandbox.kill()
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

运行方式(凭据放在环境变量中):

export USERNAME="<github-username>"
export PASSWORD="ghp_xxxxxxxxxxxxxxxxxxxx"   # Personal Access Token (classic)
export E2B_API_KEY="<your-e2b-api-key>"
python3 build_from_ghcr.py

正常输出会依次打印模板构建日志、template_idsandbox_id,以及校验标记 sandbox-ok,最后销毁 Sandbox。

上线建议

  • 凭据管理:GitHub Token 和 E2B_API_KEY 通过环境变量或密钥管理服务注入,不要写进代码或提交进 Git;Token 遵循最小权限原则,仅勾选 read:packageswrite:packages,并定期轮转。

  • 构建与运行分离:模板构建是一次性动作,构建产出的 template_id 应持久化保存并复用;不要在每次创建 Sandbox 前重复构建。

  • 镜像版本化:镜像使用明确的 tag(避免只用 latest),做到模板与镜像版本对齐、可回溯、可回滚。

  • 资源规格cpu_countmemory_mb 按镜像内工作负载评估,重依赖或大数据处理场景适当上调。

  • 及时销毁:任务结束后调用 sandbox.kill()(放在 finally 中),避免异常路径下 Sandbox 长期占用。

相关功能