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 python313

Recommended: use a teamspace-scoped API key. A teamspace-scoped key already selects its project, so you can omit --teamspace and it covers everything the CLI needs — create, run, snapshot create, and persistent stop/start. Pass --teamspace owner/teamspace only when the key is org-scoped or unset. See Authentication in the SDK reference.

Do not pass --teamspace with a teamspace-scoped key. Doing so currently hard-fails on both list and create with a misleading error — ConnectionError: Authentication failed. Please run 'lightning login'. This is a teamspace-resolution problem, not a missing-login one, so re-running lightning login will not help. Because a teamspace-scoped key already selects its project, omitting --teamspace is correct and sufficient. Pass --teamspace owner/teamspace only with an org-scoped key. The slug is the owner/teamspace pair shown in the Lightning UI (the sandbox/snapshot JSON only exposes organization_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 --help currently omits stop and update from 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's write_file / read_file. The CLI can boot from an existing snapshot via create --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

--timeout is milliseconds; run --timeout is seconds. On create, --timeout is the sandbox's max lifetime in ms (the value echoed as timeout by create --json / list --json). On run it 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 install over creating a venv in the managed runtimes. The python313 runtime already ships pip on its system interpreter (python -m pip install ... works), but python -m venv fails because ensurepip exits non-zero. In chained setup runs, pip install directly 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 GB

list

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. --limit and --page-token are accepted but ignored: list returns all sandboxes and an empty next_page_token regardless. To cap results, slice client-side (lightning sandbox list --json | jq '.sandboxes[:10]'). (Note: snapshot list --limit does 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-42

update

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 --resume

delete

Destroy a sandbox and any auto-snapshot.

lightning sandbox delete sbx-42

Running 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.py

Silent commands print Command ID: <uuid>. When the inner command produces no stdout (e.g. exit 7, mkdir), a plain (non---json) sync run prints a Command ID: <uuid> line instead of the (empty) output. Harmless, but expect it if you script on plain output — use --json and read .output for 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.py

Environment 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.py

Exit 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 fail

Check the exit code before piping --json to jq. On a failed or transient error (e.g. a 404 sandbox not found under concurrency), create --json prints a human-readable red error panel, not a JSON error object — so create --json | jq ... fails with a confusing jq: 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 create failures are often retryable.

Detached commands and --timeout

--timeout and --poll-interval are flags on run, not on command. They bound the client-side wait that run --detached performs before it returns. command, logs, and commands take no --timeout — passing it there fails with Error: 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"

--timeout elapsing is not a crash. When the wait elapses, the CLI prints a red Lightning CLI Error / TimeoutError panel and exits 1. This is the client-side wait expiring — the command keeps running server-side (same semantics as the SDK's wait()). Re-check it later with lightning 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-abc123

commands

List the sandbox's command history (cmd_id, running, exit_code, command, started_at).

lightning sandbox commands sbx-42

Command history records the executable only — not its arguments. The command field captures just the program name, so run -- 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 commands table also renders the "Command" column at zero width (it appears empty/invisible), so use commands --json and read the command field 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 --json

Snapshot 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

--name does a case-insensitive substring match on the source sandbox name. snapshot list --name box matches snapshots whose source_sandbox_name contains box (any case), e.g. My-Box and sandbox-42.

snapshot get

Show a snapshot.

lightning sandbox snapshot get snap-42

snapshot delete

Delete a snapshot.

lightning sandbox snapshot delete snap-42

Snapshot 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-all unless 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'

ports come back as JSON strings. create --json and list report ports: ["8080", "9090"] (strings, not ints) — even though --port accepts bare numbers. Harmless until you do a numeric jq comparison; quote or cast first (e.g. .ports[] | tonumber).

No public URL. --port only 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 --json before piping to jq (most important scripting pattern). On a failed or transient error (e.g. a 404 sandbox not found under concurrency), create --json prints a human-readable red error panel, not a JSON object — so create --json | jq ... fails with a confusing jq: parse error. Check the exit code first, then parse (transient create failures 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 --json to jq under run.

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?

What went wrong?

↑↓ Navigate↵ Open⌘↵ New tabesc Close