lightning-sandboxes について
lightning-sandboxes は、Lightning AI 上に隔離された仮想マシンを作成し、コードを実行するスキルです。一時的な実行環境を必要とする開発者やエージェント向けに、CLI と Python SDK、永続スナップショット、ネットワークポリシー、Docker 対応ランタイムを説明します。
主な機能
- 01起動の速い隔離仮想マシンの作成
- 02コマンド実行とファイルの読み書き
- 03ネットワークポリシーとポートの設定
- 04永続スナップショットによる停止と再開
- 05Docker ランタイムでのコンテナ構築と実行
活用シーン
- 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>`
これはエージェントの現在の環境(プロジェクトの venv、conda env など)を使用し、必要に応じてそこにインストールします。その後は、どこでも通常の 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) |
| インストールがブロックされる(読み取り専用の環境やホームディレクトリ、エージェントのサンドボックス) | 各コマンドを UV_CACHE_DIR="${TMPDIR:-/tmp}/uv" UV_TOOL_DIR="${TMPDIR:-/tmp}/uv-tools" uvx lightning-sdk … として実行します |
エージェントのサンドボックス内でのみ、lightning --version を含むすべての呼び出しで SSL/CA エラーが発生する | **/*.pem の読み取り拒否ルールによって certifi の cacert.pem が隠されています。そのパスだけを許可してください。サンドボックスを無効にしないでください |
エージェントのサンドボックス内でのみ NameResolutionError が発生する | 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 で実行してください。ユーザーのプロジェクトに残すコードについては、先に確認してから、プロジェクト独自のツール(uv add、poetry add)で lightning-sdk を依存関係として宣言してください。
組織スコープは 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 とシェルコマンド、または以下の 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 は、正常に接続が閉じられた場合はシェルの実際の終了ステータス、バックエンドから報告されない場合は 0、接続が切れた場合は -1(理由は error に記載)、実行中は None です。終了コードが確実に必要な場合は run_command を優先してください。
サンドボックス内の Docker
-docker ランタイムには Docker(エンジン、CLI、buildx、compose)が含まれており、create が返る時点ですでに dockerd が実行中なので、すぐに 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)
docker=True は、image(カスタムイメージは OCI ラベル ai.lightning.sandbox.docker=true を付けることで有効になります)または snapshot_id(ランタイムがスナップショットから作成されます)と同時に指定すると拒否されます。
イメージの保存場所と使用できる容量
cpu-1 と cpu-4 はイメージをディスクに保存し、それ以外のすべてのインスタンスタイプは RAM に保存します。 インスタンスタイプは、RAM ではなく /var/lib/docker の保存先で選んでください。ディスクを使うインスタンスタイプではイメージがメモリをまったく使用しないため、RAM が少なくても、デフォルトの cpu-1 は実用的な 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で確認してください。- ディスクを使うインスタンスタイプでは、すべてのデータに対してクォータが1つ適用されます。
/、/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 で実行されるため、存在する Docker ネットワークは host と none のみです(docker network ls で確認できます)。
- コンテナは
--network=hostを指定して実行してください。指定しないと、ネットワークに接続できません。 DNS の名前解決に失敗すると、wget: bad addressが表示されます。dockerd 自体はサンドボックスのネットスタック上で動作するため、どちらの場合もイメージの取得は可能です。 - **ビルドには
--network=hostを指定してください。指定しないと、すべてのRUNステップがfailed to solve: ... network bridge not foundで失敗します。compose では、build: {context: ., network: host}とします。 - ほかのコンテナには、compose のサービス名ではなく
127.0.0.1:<port>でアクセスしてください。 ホストネットワークにはブリッジの 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])、そのポートでサンドボックスの公開 URL にアクセスできます(sb.get_port_url(8080))。
そのまま動作する 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で失敗します(下記の DNS に関する注意点を参照)。リゾルバーを含む CIDR 許可リストを設定すると名前解決は直りますが、その後はレジストリの IP への接続が待機状態になります。Docker Hub は変動する CDN を通じてイメージを配信しています。 - 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
Raw 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は削除せずに終了します。 非対話モードでは、閉じた標準入力からプロンプトを読み込み、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を参照しています。そのため、IP アドレスへの直接接続は機能しますが、すべてのホスト名の検索がTemporary failure in name resolutionで失敗します。許可リストに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を直接呼び出した場合のみです。