关于 lightning-sandboxes
lightning-sandboxes 是用于在 Lightning AI 上创建隔离虚拟机并执行代码的技能,适合需要临时运行环境的开发者和 Agent。它介绍 CLI 与 Python SDK 操作、持久化快照、网络策略及 Docker 运行时。
主要功能
- 01创建隔离且快速启动的虚拟机
- 02执行命令并读写文件
- 03配置网络策略与端口
- 04通过持久化快照支持停止和恢复
- 05在 Docker 专用运行时中构建和运行容器
适用场景
- 01在隔离环境中运行不可信代码
- 02在干净的虚拟机中测试代码
- 03创建可停止后恢复的开发环境
- 04在沙盒中构建并检查容器
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lightning-ai/ skills --skill lightning-sandboxes安装后在 Claude Code 中输入/lightning-sandboxes确认已加载。
详细介绍
Lightning AI 沙盒
沙盒是用于执行代码、启动速度快且相互隔离的 VM。默认情况下,沙盒是临时的:停止会删除沙盒,只有设置 persistent=True 才能通过自动快照停止并恢复。请通过子包导入:from lightning_sdk.sandbox import Sandbox。顶层的 lightning_sdk.sandbox.py _Sandbox 类已弃用;请忽略。
设置与身份验证(仅适用于沙盒)
export DEBUG=0 LIGHTNING_DEBUG=0 # an inherited DEBUG=1 logs the Authorization header; set this in every new shell
# Use the Lightning AI CLI from the current env; install or upgrade it there if it's missing or older than 2026.9.18
v=$(lightning --version 2>/dev/null | sed -n 's/^Lightning CLI version //p')
[ -n "$v" ] && [ "$(printf '%s\n' 2026.9.18 "$v" | sort -V | head -1)" = 2026.9.18 ] \
|| uv pip install -U lightning-sdk || python3 -m pip install -U lightning-sdk
lightning --version # must print "Lightning CLI version …"; if not, see the table below
# `sandbox <cmd>` is also installed standalone == `lightning sandbox <cmd>`
这会使用 Agent 当前的环境,并在需要时将其安装到该环境中(项目 venv、conda 环境
等);随后在所有地方直接调用 lightning …。如果设置过程不顺利:
| 症状 | 解决方法 |
|---|---|
没有活动环境、出现 externally-managed-environment,或与项目自身的固定版本冲突 | 单独安装:uv tool install lightning-sdk(或 pipx install lightning-sdk),如果它不在 PATH 中,则调用 "$(uv tool dir --bin)/lightning" |
安装后仍没有 Lightning CLI version 行 | 另一个 lightning(PyTorch Lightning 的版本)或未激活的环境排在前面:检查 command -v lightning,然后激活环境或调用其中的 bin/lightning |
No such command、No such option 或 unexpected extra argument | CLI 版本太旧:在 command -v lightning 指向的位置升级它(uv pip install -U lightning-sdk,或 uv tool upgrade lightning-sdk) |
| 安装受阻(环境或主目录为只读,或处于 Agent 沙盒中) | 将每条命令都作为 UV_CACHE_DIR="${TMPDIR:-/tmp}/uv" UV_TOOL_DIR="${TMPDIR:-/tmp}/uv-tools" uvx lightning-sdk … 运行 |
每次调用都出现 SSL/CA 错误,即使是 lightning --version 也如此,且仅发生在 Agent 沙盒中 | **/*.pem 读取拒绝规则会隐藏 certifi 的 cacert.pem:允许访问该路径;不要禁用沙盒 |
NameResolutionError,且仅发生在 Agent 沙盒中 | 如果 lightning api 也失败,请让用户允许访问 lightning.ai 和 *.lightning.ai(/sandbox)。如果只有 studio/job 命令或 SDK 失败,说明它们绕过了沙盒代理:仅在沙盒外运行这些命令。后台轮询器也会以相同方式静默失败 |
上述安装也会让 lightning_sdk 能在该环境中导入,因此 Python 代码片段会使用其中的 python 运行。如果 CLI 来自 uv tool 或 uvx 回退方案,请使用 uv run --with lightning-sdk python script.py 运行一次性脚本。对于会留在用户项目中的代码,请先询问用户,然后使用项目自身的工具将 lightning-sdk 声明为依赖项(uv add、poetry add)。
组织范围由 API 密钥决定——没有组织标志或 LIGHTNING_ORG_ID 环境变量(使用它会被拒绝)。 沙盒需要在 LIGHTNING_SANDBOX_API_KEY 中设置组织级或团队空间级 API 密钥;个人 lightning login 凭据会失败,并显示 "Use a teamspace- or org-scoped API key (Members → API keys), not your personal login key."
你可以通过 CLI 创建组织级密钥(需要有效的个人身份验证):
export LIGHTNING_SANDBOX_API_KEY=$(lightning api-key create --org my-org --name sandbox-agent)
# or reuse the org default key: lightning api-key get --org my-org
如果用户属于多个组织,且尚未配置任何组织,请询问要使用哪个组织:
lightning api /v1/memberships | jq -r '.memberships[] | select(.ownerType=="organization") | [.ownerId, .name] | @tsv'
持久沙盒的快照/停止操作需要团队空间级密钥(组织级密钥不够)。
CLI 参考
# create — blocks until running (usually seconds)
lightning sandbox create --name devbox \
[--instance-type cpu-1] # default cpu-1 \
[--runtime python313] # DEFAULT IS node24 (Node.js, NO python!) — use python313 for Python \
# node22 | node24 | python313, each with a -docker variant \
[--image ghcr.io/org/img:latest] # custom rootfs, CPU-only; mutually exclusive with --runtime \
[--image-secret-ref <docker-registry-secret>] \
[--docker] # dockerd at boot; == appending -docker to --runtime \
[--teamspace owner/teamspace] [--persistent] [--spot] [--port 8000] \
[--snapshot-id snap-...] # warm start from a snapshot \
[--timeout 3600000] # auto-stop lifetime in MILLISECONDS \
[--json] # prints the sandbox id — capture it for later commands
lightning sandbox list [--teamspace owner/teamspace] [--limit N] [--json]
# run a command (use -- before the command); CLI exits with the command's exit code
lightning sandbox run <SANDBOX_ID> [--cwd /workspace] [--env KEY=VALUE] -- python -c "print('hi')"
lightning sandbox run <SANDBOX_ID> --detached -- bash -lc "long-task" # prints cmd_id
lightning sandbox command <SANDBOX_ID> <COMMAND_ID> # status + output
lightning sandbox logs <SANDBOX_ID> <COMMAND_ID>
lightning sandbox commands <SANDBOX_ID> # history
# lifecycle
lightning sandbox stop <SANDBOX_ID> # persistent: auto-snapshot + pause; ephemeral: == delete
lightning sandbox start <SANDBOX_ID> # resume a stopped persistent sandbox
lightning sandbox delete <SANDBOX_ID> -y # destroys sandbox AND its auto-snapshot; -y required non-interactively
# snapshots (filesystem only — running processes are not preserved)
lightning sandbox snapshot create <SANDBOX_ID> [--expiration <MS>] [--exclude PATH]
lightning sandbox snapshot list [--name N] [--teamspace owner/teamspace]
lightning sandbox snapshot get <SNAPSHOT_ID>
lightning sandbox snapshot delete <SNAPSHOT_ID> -y
运行时 ID 为 node22、node24 和 python313,每种运行时都有一个 -docker 变体。其他任何 ID 都会失败,并显示 invalid runtime: <id>。自定义 --image 会在 gVisor 下使用 CPU 运行。在 SDK 中,对应选项为 image=;对于私有注册表,则使用命名 Docker 注册表密钥的 image_secret_ref=。
没有 cp/上传 CLI。请使用 run 和 shell 命令移动文件,或使用下方的 SDK 文件 API。
Python SDK
from lightning_sdk.sandbox import Sandbox, SandboxConfig, RunCommandOpts, NetworkPolicy, PtyCreateOpts
# optional explicit config; otherwise env (LIGHTNING_SANDBOX_API_KEY) / lightning login creds are used
Sandbox.configure(api_key="...")
sb = Sandbox.create(
name="devbox",
instance_type="cpu-1",
runtime="python313", # default is node24 (Node.js only — no python!)
teamspace="owner/teamspace",
persistent=True, # enables stop()/resume()
docker=True, # dockerd at boot; see "Docker inside a sandbox"
ports=[8080], # exposes public HTTPS URLs for these ports
network_policy=NetworkPolicy(allow_cidrs=["10.0.0.0/8"]), # or "deny-all" / default open egress
timeout=30 * 60 * 1000, # auto-stop, MILLISECONDS
) # blocks until running
# commands — non-detached blocks until exit
cmd = sb.run_command("python -c 'print(42)'")
print(cmd.exit_code, cmd.output) # output = combined stdout+stderr
bg = sb.run_command(RunCommandOpts(cmd="python", args=["train.py"], cwd="/workspace",
env={"MODE": "test"}, detached=True))
bg.wait(timeout=600); print(bg.output) # or sb.kill_command(bg.cmd_id)
# files — content strings via REST; no host<->sandbox copy helper
sb.write_file("/workspace/app.py", "print('hi')")
text = sb.read_file("/workspace/out.txt") # None if missing
sb.fs.mkdir("/workspace/data", recursive=True)
sb.fs.exists("/workspace/app.py"); sb.fs.readdir("/workspace"); sb.fs.rm("/tmp/x", recursive=True)
# exposed ports (only those passed as ports=[...] at create) — proxied public HTTPS
sb.port_urls # {"8080": "https://8080-<sandbox-id>-s.cloudspaces.litng.ai"}
sb.get_port_url(8080) # ValueError if 8080 wasn't exposed at create
# normally populated by create; if empty, re-get: Sandbox().get(sb.sandbox_id).port_urls
# lifecycle
sb.extend_timeout(10 * 60 * 1000) # heartbeat; milliseconds, min 1000
snap = sb.snapshot() # filesystem snapshot; later: Sandbox.create(snapshot_id=snap.id)
auto_snap_id = sb.stop() # persistent only; sb.resume() brings it back with same id
sb.delete() # ALWAYS clean up — GC does not delete remote sandboxes
# find existing
client = Sandbox()
for s in client.list(teamspace="owner/teamspace").sandboxes: print(s.sandbox_id, s.status)
sb = client.get("sbx-...")
交互式 PTY(SDK 附带 websocket-client):sb.process.create_pty(PtyCreateOpts(session_name="main")) → pty.send_input("ls\n")、pty.wait()。连接正常关闭时,结果中的 exit_code 是 shell 的实际状态;后端未报告状态时为 0;连接中断时为 -1(error 会说明原因);运行期间为 None。如果需要保证获得退出代码,请优先使用 run_command。
沙盒中的 Docker
-docker 运行时自带 Docker(引擎、CLI、buildx、compose),并且**dockerd 在 create 返回时已经运行**,因此 docker version 立即可用。明确选择变体,或传入 --docker / docker=True,将后缀追加到基础运行时:
lightning sandbox create --runtime python313-docker --port 8080 --json # cpu-1 default is fine for Docker
lightning sandbox create --runtime python313 --docker # identical — resolves to python313-docker
lightning sandbox create --docker # no runtime -> node24-docker (Node, NOT Python)
如果同时传入 image(自定义镜像需带有 OCI 标签 ai.lightning.sandbox.docker=true 才能启用 Docker)或 snapshot_id(运行时来自快照),则不接受 docker=True。
镜像存储位置及可用空间
cpu-1 和 cpu-4 将镜像存储在磁盘上;其他所有规格都将镜像存储在 RAM 中。 选择实例类型时,应考虑 /var/lib/docker 所在位置,而不是 RAM 大小。在磁盘规格上,镜像不会占用内存,因此尽管默认的 cpu-1 RAM 较小,它仍是可用的 Docker 主机。
--instance-type | RAM | /var/lib/docker | 镜像空间 | storage_gb 能扩容吗? |
|---|---|---|---|---|
cpu-1(默认) | 1.25 GiB | 磁盘 | 5 GB | 可以 |
cpu-2 | 8.75 GiB | RAM(tmpfs) | 4.4 GB | 不可以 |
cpu-4 | 18.75 GiB | 磁盘 | 40 GB | 可以 |
cpu-8 | 38.75 GiB | RAM(tmpfs) | 20 GB | 不可以 |
cpu-16 | 77.5 GiB | RAM(tmpfs) | 39 GB | 不可以 |
df -h /var/lib/docker会报告实际上限;请以它为准,而不要只看此表。 表中是当前测得的默认值,可用于规划;在确定拉取镜像所需空间前,请先检查df。- 磁盘规格的所有内容共用一个配额。
/、/tmp和/var/lib/docker使用同一空间池,因此/root中的 3 GB 文件会让镜像可用空间减少 3 GB。通过仅限 SDK 的storage_gb扩容(Sandbox.create(..., instance_type="cpu-1", storage_gb=20)会给 Docker 提供 20 GB)。CLI 没有对应的标志。 - RAM 规格的上限是沙盒内存的一半,并计入内存预算。 镜像加上占用大量资源的进程仍可能导致内存不足而被终止。
storage_gb只会增加/的磁盘空间,不会增加镜像空间。 - 根据工作负载选择: 预算有限但需要处理大量镜像时选
cpu-1;构建时既需要空间又需要 RAM 时选cpu-4;5 GB 不够时使用storage_gb。只有在工作负载需要更多内存时才选cpu-2/cpu-8,并在这些规格上保持镜像较小。
数据根目录空间已满时,拉取操作会失败并提示 no space left on device,但沙盒会继续运行。 删除镜像后重试:
lightning sandbox run $SBX -- bash -lc 'docker system df; docker image prune -af; df -h /var/lib/docker'
较旧的集群则会终止沙盒。 在这些集群上,df 报告的空间大小毫无意义,显示为数百 GB;Docker 的限制只有沙盒内存,超出限制会导致沙盒终止:之后的每次调用都会返回 429 sandbox terminated: out of memory。请确保镜像远小于 RAM 列所示容量,或改用 cpu-1 / cpu-4。
网络:必须适配的部分
dockerd 使用 --bridge=none --iptables=false --ip6tables=false 运行,因此只存在 host 和 none 两种 docker 网络(docker network ls 可确认):
- 使用
--network=host运行容器,否则容器完全无法联网。 DNS 解析会失败,并显示wget: bad address。无论采用哪种方式,拉取镜像都可以正常工作,因为 dockerd 本身运行在沙箱的 netstack 上。 - 使用
--network=host构建,否则每个RUN步骤都会失败,并显示failed to solve: ... network bridge not found。在 compose 中:build: {context: ., network: host}。 - 通过
127.0.0.1:<port>访问其他容器,绝不能使用 compose 服务名。 Host 网络没有桥接 DNS,因此db或api会失败,并显示gaierror: [Errno -2] Name or service not known。普通的localhost可以正常解析。 -p 8080:80会被静默忽略:docker run -p以 0 退出,但不会提供任何服务。 端口发布依赖桥接网络。进程必须监听你需要的端口(将nginx配置为使用 8080,或使用uvicorn --port 8080)。如果创建沙箱时声明了该端口(--port 8080/ports=[8080],然后调用sb.get_port_url(8080)),沙箱的公开 URL 就会提供该端口上的服务。
以下 compose 模板可直接使用(FastAPI + Redis)。请以分离模式运行 docker compose up -d --build,并设置较宽裕的超时时间,因为构建过程会拉取镜像层:
services:
web:
build: {context: ., network: host} # host net so pip install reaches PyPI
network_mode: host
environment: [REDIS_HOST=127.0.0.1] # NOT "redis" — no bridge DNS
depends_on: [redis]
redis:
image: redis:7-alpine
network_mode: host
Docker 注意事项
- 网络出口受限时,镜像拉取会失败;请在默认开放网络出口的环境中拉取,或将镜像预先打包到自定义镜像中。 在
network_policy="deny-all"下,dockerd 仍会启动,但每次拉取都会卡住,最终失败,并显示Get "https://registry-1.docker.io/v2/": context deadline exceeded。允许列表中包含解析器的 CIDR(见下文 DNS 注意事项)可以修复名称解析,但拉取随后会卡在 registry IP 上,因为 Docker Hub 使用的 CDN IP 会不断变化。 - Docker Engine 固定为 v27;不要在沙箱内升级。 已知 v28+ 在此处无法正常工作,且不受支持。
docker info会显示确切的构建版本。Docker 仅支持 CPU 沙箱,绝不支持 GPU。 - 创建后无法向普通运行时添加 Docker;请预先选择
-docker运行时。 普通运行时中没有docker和dockerd,且mount会返回permission denied。 - REST API 没有
docker字段。 如果直接调用/v1/core/sandboxes,请发送"runtime": "node24-docker"。
示例工作流
此技能可处理的提示词:“在安全的地方运行这段不可信脚本”、“在干净的 VM 中测试我的代码”、“启动一个来自 ghcr.io/... 的容器并探索一下”、“给我一个重启后仍能保留状态的开发环境”。
安全地执行不可信/生成的代码(无网络出口,自动清理):
from lightning_sdk.sandbox import Sandbox
sb = Sandbox.create(name="quarantine", teamspace="my-org/my-teamspace", runtime="python313",
network_policy="deny-all", timeout=15 * 60 * 1000) # hard kill after 15 min
try:
sb.write_file("/workspace/suspect.py", open("suspect.py").read())
cmd = sb.run_command("python /workspace/suspect.py")
print(cmd.exit_code, cmd.output)
finally:
sb.delete()
通过 CLI 在干净的环境中测试代码:
SBX=$(lightning sandbox create --name test-run --teamspace my-org/my-teamspace --runtime python313 --timeout 1800000 --json | jq -r .id)
lightning sandbox run $SBX -- python --version
lightning sandbox run $SBX --cwd /workspace -- bash -lc "pip install requests && python -c 'import requests; print(requests.__version__)'"
lightning sandbox run $SBX --detached -- bash -lc "pytest -q > /workspace/test.log 2>&1" # prints cmd_id
lightning sandbox command $SBX <cmd_id> # poll status + output
lightning sandbox delete $SBX -y
运行容器化应用,并通过公开 URL 预览:
SBX=$(lightning sandbox create --name preview --runtime python313-docker \
--port 8080 --timeout 1800000 --json | jq -r .id) # cpu-1 default: images go to disk
# --network=host is mandatory, and the container must LISTEN on 8080 (-p is ignored)
lightning sandbox run $SBX -- docker run -d --network=host --name web \
-v /workspace:/srv -w /srv python:3.13-alpine python -m http.server 8080
lightning sandbox run $SBX -- curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080
lightning sandbox list --json | jq -r ".sandboxes[] | select(.id==\"$SBX\") | .port_urls[\"8080\"]"
lightning sandbox delete $SBX -y
试用自定义镜像:
lightning sandbox create --name img-test --image ghcr.io/myorg/myimage:latest \
--teamspace my-org/my-teamspace --timeout 1800000
# private registry: --image-secret-ref <docker-registry-secret-name>
使用基于快照的分支功能创建持久化开发环境:
sb = Sandbox.create(name="devbox", teamspace="my-org/my-teamspace", runtime="python313", persistent=True)
sb.run_command("pip install torch numpy pandas") # slow setup, do once
snap = sb.snapshot() # golden image
sb.stop() # pause billing; sb.resume() later, same id
fresh = Sandbox.create(name="experiment-1", snapshot_id=snap.id,
teamspace="my-org/my-teamspace") # warm clone with deps preinstalled
原始 API 回退方案
lightning api /v1/core/sandboxes -X GET -f "organizationId=${ORG_ID}" -f "projectId=${PROJECT_ID}" -f limit=20
lightning api /v1/core/sandboxes -X GET ... | jq -r '.sandboxes[] | .name // .id'
lightning api "/v1/core/sandboxes/${SANDBOX_ID}" -X GET -f "organizationId=${ORG_ID}" # -X GET: fields without it send a POST
lightning api "/v1/core/sandboxes/${SANDBOX_ID}/commands" -X POST -f command=ls -F detached=false
从成员资格中获取 ORG_ID:lightning api /v1/memberships | jq -r '[.memberships[] | select(.ownerType == "organization") | .ownerId][0]'。
注意事项
- 超时单位不同。
create --timeout、extend_timeout()和快照--expiration使用毫秒。sandbox run --timeout(分离等待)使用秒。 - 沙盒会持续计费,直到停止或删除,且垃圾回收永远不会移除它们。 务必调用
stop()/delete(),并在创建时设置timeout作为安全保障。 - 不加
-y时,sandbox delete和sandbox snapshot delete会退出而不删除。 在非交互模式下,它们会从已关闭的 stdin 读取提示,打印Are you sure you want to delete? [y/N]: Aborted.,并让沙盒继续计费。这是最容易造成费用损失的方式。 - 沙盒没有费用查询功能,因此请在创建时设置
timeout来控制预算。/v1/billing/usage、/v1/projects/<pid>/usage和/v1/core/sandboxes/<id>/usage均会返回 404。没有任何 CLI 会报告支出,而且沙盒实例类型(cpu-1、cpu-2)未列入按价格计费的加速器目录,也未出现在lightning machine list中,因此即使手动计算也无法为运行定价。 - 出口策略只能通过 SDK 设置,且创建后固定不变。
sandbox create没有网络策略标志,因此deny-all和 CIDR 允许列表需要使用lightning_sdk。这里的其他操作都可以通过 CLI 完成。无法更改运行中沙盒的出口规则。默认出口策略为开放(allow-all);对于不可信代码,请使用"deny-all"或 CIDR 允许列表。 - CIDR 允许列表本身不会放行 DNS。
allow_cidrs在 IP 层执行,而/etc/resolv.conf指向1.1.1.1和8.8.8.8。因此,所有主机名查询都会失败并返回Temporary failure in name resolution,而原始 IP 连接正常。请将1.1.1.1/32和8.8.8.8/32加入允许列表,或将沙盒指向其中的解析器。 - 从快照恢复的文件,其 mtime 会变为
1970-01-01T00:00:00Z。 零纪元时间戳会导致make、ccache和 pip/setuptools 的陈旧检查出错。请触碰依赖的文件,或避免在恢复的沙盒中使用基于 mtime 的陈旧判断逻辑。 - 沙盒内的命令以 root 身份运行。
- CLI 和 SDK 会以提示的形式返回作用域密钥错误。
Use a teamspace- or org-scoped API key ...(原始错误:"organization_id is required")→ 你使用的是个人登录密钥。Your teamspace-scoped API key is not authorized for the project requested via teamspace= ...(原始错误:"API key is not authorized for this project")→ 该密钥绑定到了其他 teamspace。This operation requires a teamspace-scoped API key ...→ 使用 org 作用域密钥创建快照或停止沙盒。只有原始lightning api调用会显示原始错误措辞。