Sandbox
Use the lightning sandbox command group to create ephemeral sandboxes, run
commands, manage files via shell, and capture snapshots from your shell, CI
pipelines, and scripts.
For Python, see the Python SDK Reference. For JavaScript, see the JS SDK Reference.
Prerequisites
Install the Lightning SDK (the CLI ships with it):
pip install lightning-sdk # Python 3.10+The CLI reads the same credentials as the SDK:
LIGHTNING_SANDBOX_API_KEY— your sandbox API key (recommended: a teamspace-scoped key).LIGHTNING_CLOUD_URL— optional control-plane URL.--api-key— every subcommand also accepts this flag, which overrides the environment variable.
export LIGHTNING_SANDBOX_API_KEY="sk-lit-..."
lightning sandbox create --name demo --instance-type cpu-1 --runtime python313Recommended: use a teamspace-scoped API key. A teamspace-scoped key already selects its project, so you can omit
--teamspaceand it covers everything the CLI needs —create,run,snapshot create, and persistentstop/start. Pass--teamspace owner/teamspaceonly when the key is org-scoped or unset. See Authentication in the SDK reference.
Do not pass
--teamspacewith a teamspace-scoped key. Doing so currently hard-fails on bothlistandcreatewith a misleading error —ConnectionError: Authentication failed. Please run 'lightning login'.This is a teamspace-resolution problem, not a missing-login one, so re-runninglightning loginwill not help. Because a teamspace-scoped key already selects its project, omitting--teamspaceis correct and sufficient. Pass--teamspace owner/teamspaceonly with an org-scoped key. The slug is theowner/teamspacepair shown in the Lightning UI (the sandbox/snapshot JSON only exposesorganization_id/project_id, not a human-readable slug).
Command overview
| Command | What it does |
|---|---|
create |
Create a sandbox and wait until it is running. |
list |
List sandboxes in a teamspace. |
run |
Run a command inside a sandbox. |
command |
Show a single command's status. |
commands |
List a sandbox's command history. |
logs |
Print command logs (one command, or all of them). |
stop |
Stop a sandbox (persistent → returns an auto-snapshot id). |
start |
Start a stopped persistent sandbox. |
update |
Update a sandbox (--resume is the supported operation). |
delete |
Destroy a sandbox. |
snapshot create |
Snapshot a sandbox's filesystem. |
snapshot list |
List snapshots. |
snapshot get |
Show a snapshot. |
snapshot delete |
Delete a snapshot. |
This table is the authoritative command list.
lightning sandbox --helpcurrently omitsstopandupdatefrom its output even though both exist and work — rely on this table for discoverability rather than--help.
No file read/write command. The CLI has no direct file command. Move files either through
run(e.g.... -- sh -c 'cat > /path') or the SDK'swrite_file/read_file. The CLI can boot from an existing snapshot viacreate --snapshot-id <id>.
Sandbox lifecycle commands
create
Create a sandbox and block until its status is running.
| Option | Details |
|---|---|
--name |
Sandbox name. Auto-generated when omitted. |
--instance-type |
Machine slug. Default cpu-1 — pass an explicit value to right-size for your workload. See Instance types. |
--runtime |
Base runtime image slug. Supported values: python313, node24, node22. Defaults to node24. Mutually exclusive with --image. |
--image |
Custom OCI image for the rootfs (CPU sandboxes only). Mutually exclusive with --runtime. |
--image-secret-ref |
Name of a project Docker-registry Secret used to pull a private --image. |
--port |
Port to expose. Repeatable. |
--teamspace |
Teamspace to own the sandbox (owner/teamspace). |
--snapshot-id |
Snapshot id to restore the filesystem from. |
--persistent / --ephemeral |
Persist state across stop via an auto-snapshot. |
--timeout |
Max sandbox lifetime in milliseconds, after which it is auto-stopped. Note: this is ms, unlike run --timeout which is seconds. |
--spot / --no-spot |
Use spot capacity. |
--api-key |
API key override. |
--json |
Print JSON instead of a table. |
lightning sandbox create --name demo --instance-type cpu-1 --runtime python313
# Custom public image
lightning sandbox create --name demo --instance-type cpu-1 \
--image docker.io/library/python:3.13-slim
# Private image with a project Docker-registry Secret
lightning sandbox create --name demo --instance-type cpu-1 \
--image ghcr.io/myorg/myimage:latest \
--image-secret-ref my-docker-registry-secret
# Auto-stop after 1 hour (timeout is in MILLISECONDS)
lightning sandbox create --name demo --runtime python313 --timeout 3600000
--timeoutis milliseconds;run --timeoutis seconds. Oncreate,--timeoutis the sandbox's max lifetime in ms (the value echoed astimeoutbycreate --json/list --json). Onrunit is a client-side wait in seconds. They are unrelated — don't carry a value from one to the other.
The deadline is fixed at create time from the CLI. There is no CLI command to extend a running sandbox's deadline; that lives only in the SDKs (
sandbox.extend_timeout(ms)in Python,sandbox.extendTimeout(ms)in JS — see the SDK reference).
The only supported --runtime values are python313, node24, and node22
(node24 is the default when --runtime is omitted). For anything else, supply
a custom image with --image. See Runtimes and
Custom Docker images in the SDK
reference.
Prefer
pip installover creating a venv in the managed runtimes. Thepython313runtime already ships pip on its system interpreter (python -m pip install ...works), butpython -m venvfails becauseensurepipexits non-zero. In chained setup runs,pip installdirectly instead of building a virtualenv.
Instance types
--instance-type selects the machine. Pick the smallest slug that fits your
workload to keep cost down. Supported CPU slugs:
| Slug | vCPU | RAM |
|---|---|---|
cpu-1 (default) |
1 | 1 GB |
cpu-2 |
2 | 8 GB |
cpu-4 |
4 | 16 GB |
cpu-8 |
8 | 32 GB |
cpu-16 |
16 | 64 GB |
lightning sandbox create --runtime python313 --instance-type cpu-4 # 4 vCPU / 16 GBlist
List sandboxes for a teamspace.
| Option | Details |
|---|---|
--teamspace |
Only list sandboxes in this teamspace (owner/teamspace). |
--limit |
Maximum number of sandboxes to return. Currently not honored — see note below. |
--page-token |
Pagination token from a previous list call. Currently not honored — see note below. |
--api-key |
API key override. |
--json |
Print JSON instead of a table. |
lightning sandbox list --teamspace owner/teamspace --limit 10
lightning sandbox list --json | jq '.sandboxes[].id'Sandbox-list paging is currently a no-op.
--limitand--page-tokenare accepted but ignored:listreturns all sandboxes and an emptynext_page_tokenregardless. To cap results, slice client-side (lightning sandbox list --json | jq '.sandboxes[:10]'). (Note:snapshot list --limitdoes work.)
stop
Stop a sandbox. For a persistent sandbox the control plane captures an
auto-snapshot and returns its id; the sandbox id keeps resolving and can be
brought back with start. For a non-persistent sandbox this is
equivalent to delete and the auto-snapshot id is empty.
lightning sandbox stop sbx-42
lightning sandbox stop sbx-42 --json # { "id": "sbx-42", "auto_snapshot_id": "snap-..." }start
Start a stopped persistent sandbox (re-applies its original network policy).
lightning sandbox start sbx-42update
Update a sandbox. The backend update route currently supports only resuming a
stopped persistent sandbox, so --resume is required — it has the same effect as
start.
lightning sandbox update sbx-42 --resumedelete
Destroy a sandbox and any auto-snapshot.
lightning sandbox delete sbx-42Running commands
run
Run a command inside a sandbox.
| Option | Details |
|---|---|
--cwd |
Working directory inside the sandbox. |
--env |
Environment variable as KEY=VALUE (repeatable) or a JSON blob. |
--detached |
Start the command and return immediately. |
--timeout |
Seconds to wait for a detached command (client-side wait only). |
--poll-interval |
Detached command poll interval (default 0.5). |
--api-key |
API key override. |
--json |
Print JSON instead of plain output. |
lightning sandbox run sbx-42 -- python -c "print('hello')"
lightning sandbox run sbx-42 --cwd /workspace --env MODE=test -- python app.pySilent commands print
Command ID: <uuid>. When the inner command produces no stdout (e.g.exit 7,mkdir), a plain (non---json) syncrunprints aCommand ID: <uuid>line instead of the (empty) output. Harmless, but expect it if you script on plain output — use--jsonand read.outputfor a stable shape.
The -- separator (important)
Put -- between the CLI's own flags and the inner command, otherwise the
inner command's flags are parsed by the CLI:
# Without --, "-c" would be eaten by the CLI
lightning sandbox run sbx-42 -- python -c "print('hello')"
# CLI flags go before --, the command and its flags go after
lightning sandbox run sbx-42 --cwd /workspace --env MODE=test -- python app.pyEnvironment variables for the inner command
--env accepts both KEY=VALUE (repeatable) and a JSON blob:
lightning sandbox run "$SBX" --env MODE=test --env DEBUG=1 -- python app.py
lightning sandbox run "$SBX" --env '{"MODE":"test","DEBUG":"1"}' -- python app.pyExit codes (CI)
A synchronous run (no --detached) propagates the inner command's exit
code as the CLI's exit code — a test exiting 7 makes lightning sandbox run
exit 7. This makes the CLI drop-in for pipeline gating:
lightning sandbox run "$SBX" -- pytest -q # CLI exits non-zero if tests failCheck the exit code before piping
--jsontojq. On a failed or transient error (e.g. a404 sandbox not foundunder concurrency),create --jsonprints a human-readable red error panel, not a JSON error object — socreate --json | jq ...fails with a confusingjq: parse error. Verify the exit code first, then parse:OUT=$(lightning sandbox create --runtime python313 --json) || { echo "$OUT"; exit 1; } SBX=$(echo "$OUT" | jq -r .id)Transient
createfailures are often retryable.
Detached commands and --timeout
--timeoutand--poll-intervalare flags onrun, not oncommand. They bound the client-side wait thatrun --detachedperforms before it returns.command,logs, andcommandstake no--timeout— passing it there fails withError: No such option: --timeout.
There are two ways to use run --detached:
# (a) Fire-and-forget — return the cmd_id immediately, inspect later yourself.
CMD=$(lightning sandbox run "$SBX" --detached --json -- bash -lc "echo start; sleep 30; echo done" | jq -r .cmd_id)
lightning sandbox command "$SBX" "$CMD" # status (no --timeout here)
lightning sandbox logs "$SBX" "$CMD" --no-timestamps
# (b) Bounded wait — put --timeout / --poll-interval on the SAME run line.
# run blocks up to 30s (polling every 1s), then returns or raises TimeoutError.
lightning sandbox run "$SBX" --detached --timeout 30 --poll-interval 1 \
-- bash -lc "echo start; sleep 30; echo done"
--timeoutelapsing is not a crash. When the wait elapses, the CLI prints a redLightning CLI Error / TimeoutErrorpanel and exits1. This is the client-side wait expiring — the command keeps running server-side (same semantics as the SDK'swait()). Re-check it later withlightning sandbox command <sbx> <cmd-id>.
command
Show a single command's status (running, exit_code, output). Takes --json
only — no --timeout. While running is yes, the exit code shows - (and
--json reports null); it is only meaningful once the command has exited.
lightning sandbox command sbx-42 cmd-abc123commands
List the sandbox's command history (cmd_id, running, exit_code, command,
started_at).
lightning sandbox commands sbx-42Command history records the executable only — not its arguments. The
commandfield captures just the program name, sorun -- sh -c "echo hi"is recorded as"command": "sh". The full argv is not captured, so you cannot reconstruct exactly what ran from history alone. If you need an audit trail, record the full command yourself at submission time.The human-readable
commandstable also renders the "Command" column at zero width (it appears empty/invisible), so usecommands --jsonand read thecommandfield to see the executable.
logs
Print command logs. Pass a command id to scope to a single command, or omit it to print the merged logs of every command in the sandbox.
| Option | Details |
|---|---|
--no-timestamps |
Print only the log messages. |
--query <text> |
Only show lines containing this substring. |
--severity <level> |
Only show lines at or above this severity (debug, info, warning, error). |
--json |
Print JSON ([{ "timestamp", "message" }]). |
lightning sandbox logs sbx-42 cmd-abc123 # one command
lightning sandbox logs sbx-42 # all commands, merged
lightning sandbox logs sbx-42 --severity warning # warnings and errors only
lightning sandbox logs sbx-42 cmd-abc123 --query ERROR --jsonSnapshot commands
snapshot create
Snapshot a sandbox's filesystem (filesystem state only, not running processes).
Waits until the snapshot is ready by default.
| Option | Details |
|---|---|
--expiration |
TTL in milliseconds (0 = never). Defaults to the platform default. |
--exclude |
Path to exclude from the snapshot. Repeatable. |
--wait / --no-wait |
Wait until the snapshot is ready (default --wait). |
--wait-timeout |
Seconds to wait for readiness (default 600). |
--api-key |
API key override. |
--json |
Print JSON instead of a table. |
lightning sandbox snapshot create sbx-42
lightning sandbox snapshot create sbx-42 --expiration 3600000 # TTL in ms (= 1h)Creating a snapshot requires a storage-backed cluster and a teamspace-scoped API key. See Snapshots in the SDK reference.
snapshot list
List snapshots.
| Option | Details |
|---|---|
--name |
Filter by source sandbox name (case-insensitive substring match). |
--teamspace |
Only list snapshots in this teamspace (owner/teamspace). |
--sort-order |
Order by creation time (asc / desc). |
--limit |
Maximum number of snapshots to return. (Honored, unlike sandbox list.) |
--page-token |
Pagination token from a previous list call. |
--api-key |
API key override. |
--json |
Print JSON instead of a table. |
lightning sandbox snapshot list --teamspace owner/teamspace --sort-order desc
lightning sandbox snapshot list --name my-box # case-insensitive substring on source name
--namedoes a case-insensitive substring match on the source sandbox name.snapshot list --name boxmatches snapshots whosesource_sandbox_namecontainsbox(any case), e.g.My-Boxandsandbox-42.
snapshot get
Show a snapshot.
lightning sandbox snapshot get snap-42snapshot delete
Delete a snapshot.
lightning sandbox snapshot delete snap-42Snapshot workflow
# Snapshot a sandbox, list snapshots, then boot a fresh sandbox from one
SNAP=$(lightning sandbox snapshot create "$SBX" --json | jq -r .id)
lightning sandbox snapshot list --teamspace owner/teamspace --sort-order desc
lightning sandbox create --snapshot-id "$SNAP" --instance-type cpu-1
lightning sandbox snapshot delete "$SNAP"Restore does not carry the source's network policy. A sandbox restored from a snapshot defaults to
allow-allunless you re-specify a policy. The CLI does not currently expose--network-policy; use the SDK to restore a locked-down box. See Network policy.
Exposing ports (--port)
create accepts --port (repeatable) to expose ports, mirroring the SDK's
ports=. The exposed numbers show up in list[].ports (and create --json's
ports).
lightning sandbox create --runtime python313 --port 8080 --port 9090
lightning sandbox list --json | jq '.sandboxes[].ports'
portscome back as JSON strings.create --jsonandlistreportports: ["8080", "9090"](strings, not ints) — even though--portaccepts bare numbers. Harmless until you do a numericjqcomparison; quote or cast first (e.g..ports[] | tonumber).
No public URL.
--portonly opens the port on the sandbox; the CLI does not return an external hostname/URL for it. Reach the port from inside the sandbox or another box on the same network, or get an endpoint from the Lightning UI / your cluster ingress. See Exposed ports.
Scripting with --json
--json is available on create, list, run, stop, command, commands,
and all snapshot subcommands.
# Capture the new sandbox id
SBX=$(lightning sandbox create --runtime python313 --json | jq -r .id)
# Run and inspect structured fields
lightning sandbox run "$SBX" --json -- python -c "print('hi')"
# { "cmd_id": "cmd-...", "exit_code": 0, "output": "hi\n", "running": false, "sandbox_id": "..." }Guard
create --jsonbefore piping tojq(most important scripting pattern). On a failed or transient error (e.g. a404 sandbox not foundunder concurrency),create --jsonprints a human-readable red error panel, not a JSON object — socreate --json | jq ...fails with a confusingjq: parse error. Check the exit code first, then parse (transientcreatefailures are often retryable):OUT=$(lightning sandbox create --runtime python313 --json) || { echo "$OUT"; exit 1; } SBX=$(echo "$OUT" | jq -e -r .id)See also Check the exit code before piping
--jsontojqunderrun.
Key JSON shapes:
| Command | Shape |
|---|---|
create / start |
Full sandbox dict (id, name, status, instance_type, persistent, runtime, image, ports, created_at, …). |
run / command |
cmd_id, exit_code, output, running, sandbox_id. |
commands |
Array of { cmd_id, command, exit_code, running, started_at }. |
stop |
id, auto_snapshot_id. |
list |
sandboxes[], total_size, next_page_token, previous_page_token. |
snapshot create / get |
A single snapshot dict with id, status, runtime, size_bytes, source_sandbox_name, auto, created_at, expires_at, … |
snapshot list |
A wrapper object: snapshots[] (each a snapshot dict as above), plus next_page_token, previous_page_token. Iterate .snapshots[], not the top level. |
Was this page helpful?
Thanks for the feedback.