主题
沙箱快照
为沙箱构建并捕获可复用的文件系统镜像。
快照是一个可复用的沙箱文件系统。当你想从一个自定义文件系统镜像启动沙箱时,从一个容器镜像构建一个自定义快照。
你也可以从一个运行中的沙箱捕获一个快照——安装软件包、写数据文件或配置状态,然后对结果打快照并作为一个新起点复用。

理解默认快照
当你在不选择快照的情况下创建沙箱时,LangSmith 用一个内置的默认快照。默认提供一个带常见开发工具的 Ubuntu 24.04 文件系统。LangSmith 可能随时间更新默认快照中包含的软件包和工具。
在自托管部署中检查默认快照文件系统
在自托管部署中,LangSmith 从一个嵌在沙箱运行时容器镜像 sandbox-host 中的文件系统创建默认快照:
- 在
sandbox-host镜像构建期间,LangSmith 从 Ubuntu 24.04 开始并安装默认开发工具。 - LangSmith 把那个文件系统打包成一个压缩的 16 GiB ext4 文件,位于
/opt/sandbox-host/bin/builder-base.ext4.gz。 - 当
sandbox-host启动时,LangSmith 用嵌入的文件系统创建默认快照。
那个压缩的 ext4 文件——而非一个单独拉取的容器镜像——才是默认快照文件系统的来源。
只扫描 sandbox-host 容器镜像并不能检查这个压缩构件内部的文件。要验证默认快照,请单独提取并扫描嵌入的文件系统。
NOTE
此过程验证默认快照文件系统。沙箱启动时 LangSmith 另外单独提供其他所需的运行时组件;它们不属于快照的一部分。
要独立检查默认快照,使用你的 Helm release 所配置的确切 sandbox-host 容器镜像。如果你镜像镜像,使用你私有仓库中 images.sandboxHostImage 里的引用。
你需要:
- 访问已部署的
sandbox-host镜像。 - 在一个 Linux 系统上,Docker、
gzip、GNUdd,以及来自e2fsprogs包的debugfs。 - 一个支持稀疏文件的本地文件系统,加上足够容纳该压缩构件和提取文件的空间。ext4 镜像有 16 GiB 的逻辑大小,但稀疏解压避免分配其清零的空闲空间。
把文件提取到一个临时目录,并用你偏好的文件系统或软件物料清单(SBOM)扫描器扫描结果目录。
设置你的部署所用的确切镜像引用,并创建一个临时工作目录:
bash
export SANDBOX_HOST_IMAGE="<sandbox-host-image-reference>"
export SNAPSHOT_WORKDIR="$(mktemp -d)"
docker pull "$SANDBOX_HOST_IMAGE"在不启动 sandbox-host 进程的情况下提取压缩的 ext4 文件:
bash
SANDBOX_HOST_CONTAINER="$(docker create "$SANDBOX_HOST_IMAGE")"
docker cp \
"${SANDBOX_HOST_CONTAINER}:/opt/sandbox-host/bin/builder-base.ext4.gz" \
"$SNAPSHOT_WORKDIR/"
docker rm "$SANDBOX_HOST_CONTAINER"把文件系统作为稀疏文件解压:
bash
gzip -dc "$SNAPSHOT_WORKDIR/builder-base.ext4.gz" | \
dd of="$SNAPSHOT_WORKDIR/builder-base.ext4" \
bs=1M conv=sparse status=progress用 debugfs 提取 ext4 内容。除非你传 -w,debugfs 以只读方式打开文件系统:
bash
mkdir -p "$SNAPSHOT_WORKDIR/rootfs"
debugfs \
-R "rdump / $SNAPSHOT_WORKDIR/rootfs" \
"$SNAPSHOT_WORKDIR/builder-base.ext4"把你的文件系统或 SBOM 扫描器指向 $SNAPSHOT_WORKDIR/rootfs。扫描器从与 LangSmith 用于默认快照的同一个根文件系统读取软件包数据库和文件。
从容器镜像构建快照
通过指向任何容器镜像来构建一个快照。该调用会阻塞直到快照就绪(默认超时 60 秒;对大镜像调高它)。
python
from langsmith.sandbox import SandboxClient
client = SandboxClient()
snapshot = client.create_snapshot(
"python",
docker_image="python:3.12-slim",
fs_capacity_bytes=1 * 1024**3, # 1 GiB
)
print(snapshot.id)ts
import { SandboxClient } from "langsmith/sandbox";
const client = new SandboxClient();
const snapshot = await client.createSnapshot(
"python",
"python:3.12-slim",
1_073_741_824, // 1 GiB
);
console.log(snapshot.id);验证一个从容器镜像构建的快照
LangSmith 从你提供的容器镜像中的文件系统构建一个自定义快照。要独立验证那些内容:
- 把源容器镜像解析为一个不可变引用,例如
registry.example.com/example/image@sha256:<digest>。 - 检查并扫描那个确切的镜像。
- 在你创建快照时,用同样的摘要限定引用作为
docker_image。
沙箱启动时 LangSmith 单独提供其所需的运行时工具。它们不会被加到你提供的容器镜像中,也不包含在自定义快照里。
私有仓库
要从私有仓库拉取,用一个凭据创建一次仓库,然后在构建快照时按 ID 引用它。仓库会持久存在,所以跨快照复用一个。
查找仓库和标签
在 LangSmith UI 中,Container Image URI 字段会从 Docker Hub 或所选私有仓库建议仓库和标签。搜索行为取决于仓库提供商和身份验证方法:
| 来源 | 仓库发现 | 标签发现 |
|---|---|---|
| Docker Hub,不带已保存的仓库 | 在一个裸镜像名或指定的 Docker Hub 命名空间上搜索 Docker Official Images | 在你选择或输入一个仓库后搜索标签 |
| Docker Registry | 当仓库支持时在仓库目录中搜索 | 在你选择或输入一个仓库后搜索标签 |
| Harbor | 在可访问的目录中搜索 | 在你选择或输入一个仓库后搜索标签 |
| GitHub Container Registry(GHCR) | 在一个指定的 GitHub owner 内搜索 | 在你选择或输入一个仓库后搜索标签 |
| Google Artifact Registry(GAR) | 在一个项目和位置内搜索仓库,然后在所选仓库内搜索软件包 | 在你选择或输入一个镜像仓库后搜索标签 |
| Amazon Elastic Container Registry(ECR),带用户名和密码 | 要求你输入一个完整的仓库 | 在你输入一个仓库后搜索标签 |
| Amazon ECR,带一个 AWS IAM 角色 | 跨配置的 AWS 账户搜索仓库 | 在你选择或输入一个仓库后搜索标签 |
仓库和标签发现是提示性的且有界限。你始终可以手动输入一个已知的仓库、标签或摘要,包括当某个提供商不支持搜索或不返回匹配时。当你需要一个不可变的快照来源时,用一个摘要限定的镜像 URI。
用用户名和密码身份验证
用户名和密码身份验证对所有受支持的私有仓库提供商都有效。对于 ECR,密码是一个 ECR 授权令牌,12 小时后过期。令牌过期时更新已保存的仓库凭据。
python
import os
registry = client.registries.create(
name="internal",
url="registry.example.com",
username="me",
password=os.environ["REGISTRY_PASSWORD"],
)
snapshot = client.create_snapshot(
"internal-python",
docker_image="registry.example.com/internal/python:3.12",
fs_capacity_bytes=2 * 1024**3,
registry_id=registry.id,
timeout=600,
)ts
const registry = await client.registries.create({
name: "internal",
url: "registry.example.com",
username: "me",
password: process.env.REGISTRY_PASSWORD,
});
const snapshot = await client.createSnapshot(
"internal-python",
"registry.example.com/internal/python:3.12",
2_147_483_648,
{
registryId: registry.id,
timeout: 600,
},
);用 client.registries.list()、client.registries.retrieve(name)、client.registries.update(name, ...) 和 client.registries.delete(name) 来列出、检查、更新和删除仓库。
用 AWS IAM 角色向 ECR 身份验证
AWS IAM 角色身份验证避免了在 LangSmith 中存储一个会过期的 ECR 授权令牌。它支持商业 AWS 分区中的私有 ECR 仓库。公共 ECR、AWS GovCloud、AWS China 和自定义 ECR 兼容域继续使用用户名和密码身份验证。
NOTE
AWS IAM role 身份验证方法只在你的 LangSmith 部署支持它时才出现。如果该选项不可用,使用用户名和密码身份验证。
要注册一个 ECR 角色:
- 在 LangSmith 中,打开 Sandboxes > Registries,选择 Register Private Registry,并在表单中输入你的仓库 URL
<aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com。 - 在 Authentication method 下选择 AWS IAM role。复制 LangSmith 显示的 AWS principal ARN 和工作区 ID。LangSmith 把工作区 ID 用作
sts:ExternalId。 - 在 AWS 中,创建一个 IAM 角色或用一条使用 LangSmith 中所示确切值的信任策略更新一个现有角色:
json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "<principal-arn-shown-in-langsmith>"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "<workspace-id-shown-in-langsmith>"
}
}
}
]
}- 给该角色发现和拉取所需 ECR 镜像的权限。下面的策略授予对单个 AWS 账户和区域中每个仓库的访问。替换占位符,或把仓库资源收窄到 LangSmith 能使用的仓库:
json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GetAuthorizationToken",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "DiscoverAndPullImages",
"Effect": "Allow",
"Action": [
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
],
"Resource": "arn:aws:ecr:<aws-region>:<aws-account-id>:repository/*"
}
]
}- 对托管 LangSmith,把
LangSmithSandboxECR=true标签加到该 IAM 角色。 - 返回 LangSmith,在 AWS role ARN 中输入角色 ARN,然后注册私有仓库。
LangSmith 分别为仓库发现、标签发现和每次快照构建尝试承担该角色。它为延迟的作业和重试获取一个新鲜的 ECR 授权令牌,且不持久化临时 AWS 凭据或生成的 ECR 令牌。
对于自托管部署,管理员必须设置 SANDBOX_ECR_AWS_ROLE_PRINCIPAL_ARN 并为平台后端和快照 worker 提供一个兼容的 AWS SDK 凭据来源。保持该设置未设会隐藏 AWS IAM 角色身份验证并保留用户名和密码身份验证。
从 Dockerfile 构建快照
当你有一个本地 Dockerfile 但想先不把镜像发布到仓库时,直接从 Dockerfile 及其构建上下文构建一个快照。LangSmith 拉起一个临时的构建器沙箱,上传上下文,在其中用 BuildKit 运行构建,并把结果镜像捕获为一个快照。构建一旦完成,构建器沙箱会自动拆除。
该调用会阻塞直到快照就绪(默认超时 60 秒;对大而慢的构建调高它)。fs_capacity_bytes 必须足够大以容纳构建上下文、中间层和最终镜像。
python
from langsmith.sandbox import SandboxClient
client = SandboxClient()
snapshot = client.create_snapshot_from_dockerfile(
"my-app",
dockerfile="Dockerfile",
fs_capacity_bytes=2 * 1024**3, # 2 GiB
context=".", # build context directory (default: current directory)
)
print(snapshot.id)ts
import { SandboxClient } from "langsmith/sandbox";
const client = new SandboxClient();
const snapshot = await client.createSnapshotFromDockerfile(
"my-app",
"Dockerfile",
2_147_483_648, // 2 GiB
{ context: "." },
);
console.log(snapshot.id);NOTE
除非你传一个绝对路径,dockerfile 都相对 context 解析,且它必须位于上下文目录内。.git 目录会被自动从上传的上下文中排除。
构建参数与目标阶段
传 build_args / buildArgs 设置 Docker ARG 值,并传 target 停在多阶段构建的某个特定阶段。
python
snapshot = client.create_snapshot_from_dockerfile(
"my-app",
dockerfile="Dockerfile",
fs_capacity_bytes=2 * 1024**3,
build_args={"PYTHON_VERSION": "3.12", "ENV": "prod"},
target="runtime",
)ts
const snapshot = await client.createSnapshotFromDockerfile(
"my-app",
"Dockerfile",
2_147_483_648,
{
buildArgs: { PYTHON_VERSION: "3.12", ENV: "prod" },
target: "runtime",
},
);流式传输构建日志
传一个回调给 on_build_log / onBuildLog,以便在构建运行时接收其 stdout 和 stderr,这对浮现进度或调试失败的构建很有用。
python
snapshot = client.create_snapshot_from_dockerfile(
"my-app",
dockerfile="Dockerfile",
fs_capacity_bytes=2 * 1024**3,
on_build_log=lambda line: print(line, end=""),
)ts
const snapshot = await client.createSnapshotFromDockerfile(
"my-app",
"Dockerfile",
2_147_483_648,
{ onBuildLog: (line) => process.stdout.write(line) },
);加速冷构建
vcpus / vCpus 和 mem_bytes / memBytes 为临时构建器沙箱定大小。构建在其中运行 BuildKit 加上原生快照器的层拷贝,它们争用构建器默认的 0.5 vCPU,因此给构建器更多 CPU 能大幅削减冷构建的墙钟时间。内存按每 vCPU 4 GiB 与 CPU 绑定,且必须保持在该目标的 50% 以内,所以一个 2-vCPU 构建器接受 4 到 12 GiB。省略内存则它遵循该比例。
python
snapshot = client.create_snapshot_from_dockerfile(
"my-app",
dockerfile="Dockerfile",
fs_capacity_bytes=2 * 1024**3,
vcpus=2,
mem_bytes=8 * 1024**3, # 8 GiB
timeout=600,
)ts
const snapshot = await client.createSnapshotFromDockerfile(
"my-app",
"Dockerfile",
2_147_483_648,
{
vCpus: 2,
memBytes: 8_589_934_592, // 8 GiB
timeout: 600,
},
);TIP
同步的 SandboxClient 和 AsyncSandboxClient 都以相同参数暴露这个方法——在异步客户端上 await client.create_snapshot_from_dockerfile(...)。
从运行中的沙箱捕获快照
从一个现有快照启动一个沙箱,安装软件包或准备数据,然后把结果捕获为一个新快照。返回的快照把它的 source_sandbox_id 设为它被捕获自的那个沙箱,并可用作任何后续 create_sandbox 调用的 snapshot_id。
python
sb = client.create_sandbox(snapshot_id=base_snapshot_id, name="setup-box")
sb.run("pip install numpy pandas scikit-learn", timeout=180)
sb.write("/opt/config.yaml", "model: gpt-5\n")
# Capture the current filesystem as a new snapshot
snapshot = sb.capture_snapshot("ml-ready")
print(snapshot.id, snapshot.source_sandbox_id)
sb.delete()
# Boot fresh sandboxes pre-loaded with those dependencies
with client.sandbox(snapshot_id=snapshot.id) as sb:
sb.run("python -c 'import numpy; print(numpy.__version__)'")
assert sb.read("/opt/config.yaml") == b"model: gpt-5\n"ts
const running = await client.createSandbox(baseSnapshotId, { name: "setup-box" });
await running.run("pip install numpy pandas scikit-learn", { timeout: 180 });
await running.write("/opt/config.yaml", "model: gpt-5\n");
const snapshot = await running.captureSnapshot("ml-ready");
console.log(snapshot.id, snapshot.source_sandbox_id);
await running.delete();
const sandbox = await client.createSandbox(snapshot.id);
try {
await sandbox.run("python -c 'import numpy; print(numpy.__version__)'");
const cfg = await sandbox.read("/opt/config.yaml");
console.log(new TextDecoder().decode(cfg));
} finally {
await sandbox.delete();
}NOTE
默认情况下,捕获只保留文件系统。安装的软件包(在 /usr/local、/root、/opt、家目录等之下)和你写到那些位置的文件会被保留,/tmp 亦然。只有 /dev/shm 是 tmpfs,所以其他一切都在沙箱的磁盘上。运行中的进程、打开的套接字和内存中的状态不被带过去:启动新沙箱并重新运行你需要的进程,或也捕获内存。
TIP
你可以按名字而非 ID 从一个快照启动沙箱——当你记得捕获时用的人类可读标签时很方便:
python
sb = client.create_sandbox(snapshot_name="ml-ready")ts
const sb = await client.createSandbox({ snapshotName: "ml-ready" });至多传入 snapshot_id / snapshot_name(或 TypeScript 中的 snapshotId / snapshotName)之一。两者都省略以使用默认运行时。
调优捕获时机
capture_snapshot 会阻塞直到新快照就绪。如果你的文件系统很大或你的存储后端很慢,调高 timeout 关键字参数(默认 60 秒)。
python
snapshot = sb.capture_snapshot("ml-ready-v2", timeout=600)ts
const snapshot = await sb.captureSnapshot("ml-ready-v2", { timeout: 600 });从内存恢复
一个快照可以在其文件系统之外携带沙箱的 RAM。从这样一个快照启动,沙箱会从中断处恢复,进程仍在运行,而非冷启动。把这用于预热慢的环境,例如一个已加载的模型或一个已启动的数据库。
NOTE
内存快照仅通过 REST API 可用。langsmith.sandbox 的 Python 和 TypeScript 客户端尚未暴露这些字段。
通过在捕获上设置 include_memory 来捕获内存:
bash
curl -X POST \
"$LANGSMITH_ENDPOINT/api/v2/sandboxes/boxes/my-vm/snapshot" \
-H "x-api-key: $LANGSMITH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "warm-model", "include_memory": true}'当捕获了内存时,响应会报告 memory_snapshot_size_bytes。include_memory 需要一个正在运行或已停止的沙箱,且它不能与 checkpoint 或 docker_image 组合。
有些沙箱运行在一个无法携带内存镜像的 overlay 文件系统运行时上。捕获一个这样的沙箱会返回 include_memory is not supported for overlay-rootfs sandboxes。LangSmith 指派那个运行时,所以这不是你按沙箱选择的东西。
create 上的两个字段控制另一半:
| 字段 | 它做什么 |
|---|---|
restore_memory | 省略它以在快照有内存时从内存恢复、在它没有时冷启动。true 要求有内存,若快照没有内存会使请求失败。false 总是冷启动。 |
preserve_memory_on_stop | true 在一次自愿停止(空闲超时或显式停止)时挂起 RAM,使沙箱在下次唤醒时从中断处恢复,而非冷启动。默认为 false,只保留文件系统。由基础设施维护触发的重启无论如何都保留内存。 |
bash
curl -X POST "$LANGSMITH_ENDPOINT/api/v2/sandboxes/boxes" \
-H "x-api-key: $LANGSMITH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "warm-vm",
"snapshot": "warm-model",
"restore_memory": true,
"preserve_memory_on_stop": true
}'从一个已停止的沙箱捕获内存,只在该沙箱是用 preserve_memory_on_stop 创建时才可行。没有它,停止会丢弃 RAM,也就没有可捕获的东西。
列出、获取和删除快照
python
# List all snapshots in the workspace
snapshots = client.list_snapshots()
for s in snapshots:
print(s.id, s.name, s.status)
# Fetch a single snapshot by ID
snapshot = client.get_snapshot("550e8400-e29b-41d4-a716-446655440000")
# Delete a snapshot (fails if any sandbox still references it)
client.delete_snapshot(snapshot.id)ts
const snapshots = await client.listSnapshots();
for (const s of snapshots) {
console.log(s.id, s.name, s.status);
}
const snapshot = await client.getSnapshot("550e8400-e29b-41d4-a716-446655440000");
await client.deleteSnapshot(snapshot.id);NOTE
list_snapshots / listSnapshots 在服务端分页(默认页大小 50,最大 500)并接受可选过滤器:name_contains / nameContains(对名字的大小写不敏感子串)、limit(1–500)和 offset(≥ 0)。通过推进 offset 来翻页结果。
python
page = client.list_snapshots(name_contains="ml", limit=100)ts
const page = await client.listSnapshots({ nameContains: "ml", limit: 100 });已停止的沙箱
一个已停止的沙箱保留其文件系统,下一个请求会自动唤醒它。你不需要自己启动它:发送你想运行的命令,沙箱就会回来服务它。
python
sb = client.create_sandbox(snapshot_id=snapshot.id, name="my-vm")
sb.run("echo 'hello' > /tmp/state.txt")
# Stop early to release resources. The idle timeout does this for you.
sb.stop()
# No start call: this wakes the sandbox and runs once it is up.
result = sb.run("cat /tmp/state.txt")
assert result.stdout.strip() == "hello"ts
const sb = await client.createSandbox(snapshot.id, { name: "my-vm" });
await sb.run("echo 'hello' > /tmp/state.txt");
await sb.stop();
const result = await sb.run("cat /tmp/state.txt");
console.log(result.stdout.trim()); // "hello"停止后的第一个请求付出启动开销,所以它比随后的请求慢。用 preserve_memory_on_stop 创建沙箱以从内存恢复,而非冷启动。