How Do I Set Up a Python Dev Environment in a Cloud Sandbox With uv?
Updated October 27, 2026 .md
Short answer: A new sandbox already has Python 3.12.3 and uv 0.13.1. Run uv python install for another version, clone your project, run uv sync --frozen and uv run pytest. Connect VS Code through sbx ssh-config, then save the sandbox as a named snapshot so the next one starts with Python, venv and cache ready.
We ran every command on this page on prod on 2026-10-27, on small sandboxes (2 vCPU, 4 GB) in eu-central. The project is pallets/click at commit 2247b35: a real library with a uv.lock and about 2,300 tests.
Results#
| Step | Time on small |
|---|---|
uv python install 3.13 (33.5 MiB download) |
1.0 s |
uv sync --frozen, empty cache (3 runs) |
0.98, 0.98, 1.02 s |
uv sync --frozen, warm cache, no .venv (3 runs) |
0.08, 0.08, 0.08 s |
uv sync --frozen, nothing to do (3 runs) |
0.01 s |
uv run pytest, Python 3.13 |
2262 passed in 5.5 s (9.5 s wall) |
uv run pytest, system Python 3.12 |
2262 passed in 4.2 s (7.8 s wall) |
sbx new --from the snapshot, SDK create() to ready |
2.6 s |
In the new sandbox: uv sync, as saved / cache only (3 runs each) |
0.01 s / 0.19 s |
The lesson: with uv, the install is rarely what you wait for. Our cold sync was about one second. A snapshot still helps because it also holds the extra Python, the clone and anything slow you add later, such as apt packages or a source-built wheel.
1. See what the sandbox already has#
sbx new --name pydev-1 --type small
sbx exec sb_bpznbn5i6o bash -lc "python3 --version; which python3 uv; uv --version; uv python list --only-installed; uv cache dir"Python 3.12.3
/usr/bin/python3
/usr/local/bin/uv
uv 0.13.1 (x86_64-unknown-linux-musl)
cpython-3.12.3-linux-x86_64-gnu /usr/bin/python3.12
cpython-3.12.3-linux-x86_64-gnu /usr/bin/python3 -> python3.12
/home/user/.cache/uvThe system Python is Ubuntu's 3.12.3, and uv, uvx, pip and pipx are installed. These are the versions on 2026-10-27; the image changes over time. The full inventory is in What's pre-installed in a sandbox.
2. Install another Python#
sbx exec sb_bpznbn5i6o --timeout 300 bash -lc "uv python install 3.13 && uv run --python 3.13 --no-project python -c 'import sys; print(sys.version)'"Downloading cpython-3.13.16-linux-x86_64-gnu (download) (33.5MiB)
Installed Python 3.13.16 in 999ms
+ cpython-3.13.16-linux-x86_64-gnu (python3.13)
3.13.16 (main, Oct 14 2026, 01:04:01) [Clang 23.1.3 ]uv puts managed Pythons in ~/.local/share/uv/python and links python3.13 into ~/.local/bin. Both are under /home/user, so they are kept by sbx stop and saved in a snapshot. With 3.13 installed, uv picked it over the system 3.12 for the project below: uv prefers its own managed Pythons by default.
3. Sync a real project and run its tests#
sbx exec sb_bpznbn5i6o --timeout 600 bash -lc '
git clone -q https://github.com/pallets/click && cd click && git checkout -q 2247b35ea1c4
uv sync --frozen
uv run --frozen pytest -q -p no:cacheprovider | tail -1'…
+ uv==0.11.3
+ virtualenv==21.2.0
2262 passed, 24 skipped, 31000 deselected, 1 xfailed in 5.47s--frozen installs exactly what uv.lock pins and fails rather than re-resolving. click's pyproject.toml sets default-groups (dev, tests, typing, pre-commit), so a plain uv sync installs pytest, mypy and pyright too. The 31,000 deselected tests are click's stress tests, which its addopts skip by default.
To run the same suite on the system Python without touching the main venv, point uv at a second environment:
cd ~/click && UV_PROJECT_ENVIRONMENT=.venv-312 uv run --frozen --python 3.12 pytest -q -p no:cacheprovider | tail -12262 passed, 24 skipped, 31000 deselected, 1 xfailed in 4.24sOne detail matters for snapshots. uv hardlinks packages from its cache into .venv, so the two share disk. In our sandbox du reported 194 MB for .venv and 195 MB for the cache on their own, but only 200 MB for both together. Saving both costs about the same as saving one.
4. Open it in VS Code#
Run sbx ssh-config --install once on your laptop. It adds a Host *.sbx entry that tunnels SSH through the CLI over HTTPS:
Host *.sbx
User user
IdentityFile ~/.ssh/sandboxer_ed25519
IdentitiesOnly yes
ProxyCommand sbx proxy %h
…Then any SSH tool reaches the sandbox by id:
ssh sb_bpznbn5i6o.sbx 'cd click && uv run --frozen which python && git log -1 --format=%h'/home/user/click/.venv/bin/python
2247b35In VS Code or Cursor, run Remote-SSH: Connect to Host…, enter sb_bpznbn5i6o.sbx, open /home/user/click, and choose /home/user/click/.venv/bin/python as the interpreter. The Python extension then runs and debugs tests in the sandbox's venv. We tested the ssh command above, not the VS Code UI; the editor setup, Cursor and troubleshooting are in SSH into a cloud sandbox from VS Code or Cursor. For a browser-only editor, see Run code-server.
5. Save it as a named snapshot#
sbx snapshot sb_bpznbn5i6o pydev-uv{"ok":true,"type":"snapshot.named.info","snapshot":{…,"name":"pydev-uv",…,"status":"ready","type":"small",…}}The save took 2.1 seconds. The snapshot holds the whole disk: Python 3.13, the clone, .venv and ~/.cache/uv. You can delete the source sandbox afterwards; the snapshot stays. To version the snapshot by lockfile hash and rebuild it automatically when uv.lock changes, follow Cache dependencies in a named snapshot.
6. Use the Python SDK from outside and inside#
Outside, on your laptop or CI runner, Sandboxer() reads SANDBOXER_API_KEY. This script starts a sandbox from the snapshot, times uv sync and the tests, and deletes the sandbox. Install the SDK with pip install sandboxer-sdk (the package named sandboxer on PyPI is an unrelated project).
import time
from sandboxer import Sandboxer
with Sandboxer() as client: # reads SANDBOXER_API_KEY
t = time.monotonic()
sb = client.create(type="small", from_="pydev-uv", name="pydev-2")
print(f"{sb.id} ready in {time.monotonic() - t:.1f} s")
try:
steps = [
("as saved", "cd ~/click && uv sync --frozen -q"),
("cache only", "cd ~/click && rm -rf .venv && uv sync --frozen -q"),
("tests", "cd ~/click && uv run --frozen pytest -q -p no:cacheprovider | tail -1"),
]
for label, cmd in steps:
run = sb.exec(["bash", "-lc", f"TIMEFORMAT=%R; {{ time ( {cmd} ) ; }} 2>&1"], timeout_seconds=300)
out = run.stdout.strip().splitlines()
print(f"{label}: exit {run.exit_code}, {' | '.join(out[-2:])}")
finally:
sb.delete()
print("deleted", sb.id)Our run repeated the first two steps three times each:
sb_5bp3zm3lue ready in 2.6 s
as saved: exit 0, 0.046
as saved: exit 0, 0.009
as saved: exit 0, 0.008
cache only: exit 0, 0.274
cache only: exit 0, 0.155
cache only: exit 0, 0.192
tests: exit 0, 2262 passed, 24 skipped, 31000 deselected, 1 xfailed in 4.19s | 7.964
deleted sb_5bp3zm3lueThe managed Python 3.13 came with the snapshot. Nothing was downloaded, and the tests ran on 3.13 without a reinstall.
Inside the sandbox, SANDBOXER_TOKEN and SANDBOXER_API_URL are already set, so Sandboxer() needs no arguments. That token is limited to its own sandbox:
# whoami_sbx.py, run in the sandbox
import os
from sandboxer import Sandboxer, SandboxerError
with Sandboxer() as client: # reads SANDBOXER_TOKEN and SANDBOXER_API_URL
me = client.get(os.environ["SANDBOXER_ID"])
print(me.id, me.name, me.state)
print(sum(1 for _ in client.list()), "sandbox(es) visible to this token")
try:
client.create(type="small", wait=False)
except SandboxerError as e:
print(e.status, e.code, e.message)uv run --no-project --with sandboxer-sdk python ~/whoami_sbx.pysb_bpznbn5i6o pydev-1 ready
1 sandbox(es) visible to this token
403 api_key_action_forbidden This API key does not allow sandbox.create.We ran the lookup and the create call as two separate scripts; the output lines are theirs. uv run --with fetched the SDK and its 11 dependencies into a throwaway environment in under 2.5 seconds, so it never touches the project's venv.
| Outside (laptop, CI) | Inside the sandbox | |
|---|---|---|
| Key | Your SANDBOXER_API_KEY |
SANDBOXER_TOKEN, set for you |
| Can create, fork, snapshot, delete | Yes, as your key's scope allows | No (403) |
| Can exec, read and write files, host ports | Any sandbox your key reaches | Only its own sandbox |
| Use it for | Orchestrating sandboxes, CI | Code in the sandbox that needs its own id, files or hosted URL |
FAQ#
Which Python version does a Sandboxer sandbox have?#
On 2026-10-27, Ubuntu's Python 3.12.3 at /usr/bin/python3, with uv 0.13.1. uv python install 3.13 added 3.13.16 in about one second. The image is updated over time, so check python3 --version in your own sandbox.
Should I use pip or uv in a sandbox?#
Either works. uv is already installed, and in our test it synced click's 32 locked packages in about one second from an empty cache. For a pip comparison on a larger project, see Cache dependencies in a named snapshot.
Does the venv survive sbx stop and sbx resume?#
Yes. The venv, uv's cache and uv-installed Pythons all live under /home/user, which is on the sandbox disk. Stop keeps the disk, and a named snapshot or fork copies it.
Can I make the snapshot a different size than the sandbox I built it in?#
A sandbox from a snapshot takes the snapshot's size unless you pass --type. Moving to a smaller size isn't supported yet, so build the snapshot on the smallest size you plan to use.
Can code inside the sandbox start more sandboxes with the SDK?#
Not with the built-in SANDBOXER_TOKEN. It returned 403 api_key_action_forbidden for create in our test. Orchestrate from outside with your own API key.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.