Loading...
Loading...
Run code in Lightning AI Sandboxes - fast, isolated ephemeral VMs with optional persistence, snapshots, custom images, Docker-in-sandbox (docker / docker compose), network egress policies, file I/O, public port URLs, and interactive PTY sessions. Use when the user wants to execute untrusted or experimental code safely, needs a throwaway cloud VM, wants to build/run containers or preview a containerized app, or asks about lightning.ai sandboxes or snapshots.
npx skill4agent add lightning-ai/skills lightning-sandboxespersistent=Truefrom lightning_sdk.sandbox import Sandboxlightning_sdk.sandbox.py_Sandbox# 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>`lightning …| Symptom | Fix |
|---|---|
Both installs fail: no active env, or pip refuses with | Install it on its own instead: |
| |
| The CLI is older than this skill expects: |
| The upgrade fails on a version conflict with the project's own pins | Don't fight the pins: install it outside the project with |
| Installing is blocked (read-only env or home, agent sandbox) | Skip it and run each command as |
Every call fails on SSL/certificates, even | A |
lightning_sdkpythonuvxuv run --with lightning-sdk python script.pylightning-sdkuv addpoetry addLIGHTNING_ORG_IDLIGHTNING_SANDBOX_API_KEYlightning loginexport 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-orglightning api /v1/memberships | jq -r '.memberships[] | select(.ownerType=="organization") | [.ownerId, .name] | @tsv'# 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> -ycprunfrom 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-...")websocket-clientsb.process.create_pty(PtyCreateOpts(session_name="main"))pty.send_input("ls\n")pty.wait()exit_code0-1errorNonerun_command-dockerdockerddocker version--dockerdocker=Truelightning 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)node22-dockernode24-dockerpython313-dockerinvalid runtime: <id>docker=Trueimageai.lightning.sandbox.docker=truesnapshot_id/var/lib/dockercpu-1cpu-4cpu-1cpu-2 | RAM | | image room | |
|---|---|---|---|---|
| 1.25 GiB | disk | 5 GB | yes |
| 8.75 GiB | RAM (tmpfs) | 4.4 GB | no |
| 18.75 GiB | disk | 40 GB | yes |
| 38.75 GiB | RAM (tmpfs) | 20 GB | no |
| 77.5 GiB | RAM (tmpfs) | 39 GB | no |
df -h /var/lib/docker//tmp/var/lib/docker/rootstorage_gbSandbox.create(..., instance_type="cpu-1", storage_gb=20)storage_gb/df -h /var/lib/dockercpu-1cpu-4storage_gbcpu-2cpu-8no space left on devicelightning sandbox run $SBX -- bash -lc 'docker system df; docker image prune -af; df -h /var/lib/docker'dockerd--bridge=none --iptables=false --ip6tables=falsehostnonedocker network ls--network=hostwget: bad address--network=hostbuild: {context: ., network: host}RUNfailed to solve: ... network bridge not found127.0.0.1:<port>dbapigaierror: [Errno -2] Name or service not knownlocalhost-p 8080:80nginxuvicorn --port 8080docker run -p:8080--port 8080ports=[8080]sb.get_port_url(8080)docker compose up -d --buildservices:
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: hostcpu-1cpu-4storage_gbno space left on device429 sandbox terminated: out of memorycpu-1cpu-4network_policy="deny-all"Get "https://registry-1.docker.io/v2/": context deadline exceeded1.1.1.1/328.8.8.8/32docker infodockerdockerdmountpermission denied-dockerdocker/v1/core/sandboxes"runtime": "node24-docker"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()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 -ySBX=$(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 -ylightning 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 preinstalledlightning 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=falseORG_IDlightning api /v1/memberships | jq -r '[.memberships[] | select(.ownerType == "organization") | .ownerId][0]'create --timeoutextend_timeout()--expirationsandbox run --timeoutstop()delete()timeoutsandbox deletesandbox snapshot delete-y--yesAre you sure you want to delete? [y/N]: Aborted.allow_cidrs/etc/resolv.conf1.1.1.18.8.8.8Temporary failure in name resolution1.1.1.1/328.8.8.8/321970-01-01T00:00:00Zmakeccachesandbox createdeny-alllightning_sdk/v1/billing/usage/v1/projects/<pid>/usage/v1/core/sandboxes/<id>/usagecpu-1cpu-2lightning machine listtimeoutpersistent=Truenode24runtime="python313"--runtime python313node22node24python313-dockerimageruntimeimage_secret_ref-docker--runtime python313-dockerdocker=True--docker--network=hostports=[8080]--port 8080createport_urlsgetallow-all"deny-all"Use a teamspace- or org-scoped API key ...Your teamspace-scoped API key is not authorized for the project requested via teamspace= ...This operation requires a teamspace-scoped API key ...lightning api