How to install Python packages with uv in a sandbox
uv is preinstalled: run uv venv, then uv pip install <package>, or uv sync in a project that has a uv.lock.
On Runtime uv is part of the default image, so there is nothing to install
before you install. Every sandbox has uv 0.12.17 (uv and uvx) beside
Python 3.12 and pip (what is installed),
inside a Firecracker microVM of its own where a package's install script runs
away from your machine. uv's own documentation calls it "10-100x faster than
pip" and a drop-in replacement for common pip commands. A new sandbox ran its
first Python command 351 ms after the create request at the median on
24 September 2026 (speed).
Install into a virtual environment
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create();const slow = { check: true, timeoutMs: 300_000 } as const;await sbx.exec("uv venv /workspace/.venv", slow);await sbx.exec("uv pip install polars httpx", { ...slow, cwd: "/workspace" });const run = await sbx.exec([".venv/bin/python", "-c", "import polars; print(polars.__version__)"]);console.log(run.stdout);Pythonfrom withruntime import Sandboxwith Sandbox.create() as sbx: sbx.exec("uv venv /workspace/.venv", check=True, timeout_ms=300_000) sbx.exec("uv pip install polars httpx", cwd="/workspace", check=True, timeout_ms=300_000) run = sbx.exec([".venv/bin/python", "-c", "import polars; print(polars.__version__)"]) print(run.stdout)Terminalruntime sandbox exec "${id}" -- uv venv /workspace/.venvruntime sandbox exec "${id}" --cwd /workspace --timeout 300 -- uv pip install polars httpxuv pip install finds .venv in the working directory, which is why the
second command runs in /workspace. It looks for a virtual environment rather
than writing into the system Python.
Install a project from its lockfile
A project with pyproject.toml and uv.lock installs exactly the versions it
locked. Upload it, sync, and run:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 1800 });await sbx.files.upload("./service", "/workspace/service");const inProject = { cwd: "/workspace/service", check: true, timeoutMs: 600_000 } as const;await sbx.exec("uv sync --frozen", inProject);const tests = await sbx.exec("uv run pytest -q", { ...inProject, check: false });console.log(tests.exitCode, tests.stdout);Pythonfrom withruntime import Sandboxwith Sandbox.create(timeout_seconds=1800) as sbx: sbx.files.upload("./service", "/workspace/service") sbx.exec("uv sync --frozen", cwd="/workspace/service", check=True, timeout_ms=600_000) tests = sbx.exec("uv run pytest -q", cwd="/workspace/service", timeout_ms=600_000) print(tests.exit_code, tests.stdout)--frozen uses the lockfile as it is, without checking whether it is up to
date, so a sandbox runs what your laptop and CI ran. For a one-off tool, uvx ruff check . runs it in a
throwaway environment, and uv run --with requests script.py adds a package
for one run.
Start every sandbox with the environment ready
Build the environment into a custom image. Recipe commands run
as root, so put the environment outside /workspace and call its Python by
path:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "uv-data", recipe: { commands: ["uv venv /opt/venv", "uv pip install --python /opt/venv/bin/python polars duckdb"], },});await using sbx = await runtime.sandboxes.create({ image: "uv-data" });await sbx.exec(["/opt/venv/bin/python", "-c", "import duckdb, polars"], { check: true });Building is free. A stored image costs $0.08 per decimal GB per 30-day month,
and the free trial stores your first three free
(pricing). A recipe's pip list
is the shorter path when plain pip is enough.
uv or pip?
| Task | uv | pip in the sandbox |
|---|---|---|
| Quick install for one job | uv venv then uv pip install ... |
pip install ..., no environment needed |
| Exact versions from a lockfile | uv sync --frozen |
pip install -r requirements.txt (pinned) |
| Run a CLI tool once | uvx <tool> |
pip install then run it |
| Another Python version | uv run --python 3.13 ... downloads it |
Not with pip |
| Where packages go | The virtual environment you made | /workspace/.local as the user, /usr/local under sudo |
Plain pip install works without a virtual environment here: the image's
/etc/pip.conf sets break-system-packages, so Ubuntu's PEP 668 guard does
not stop it (who you are).
Mistakes and how Runtime handles them
uv pip installwith no environment. uv expects a virtual environment and does not change the system Python unless told to with--system. Make one withuv venv, or use pip for a system-wide install.- A command that times out. A large install can pass the 60-second default
for a command. Give it
timeoutMs; a timeout returns the output so far. - An allow list without the package index. With narrowed network rules,
allow
pypi.organd*.pythonhosted.org, or install before turning the internet off (turn off sandbox internet). - Keeping the key to a private index. Store it as a secret the sandbox never sees: the proxy adds it on HTTPS requests to the index's host only.
- Disk. Wheels for large libraries fill a small disk. The default 4 GiB
sandbox had about 2.5 GiB free on 24 September 2026; ask for more with
diskMiB.
Related
- Run untrusted Python code safely.
- Install apt packages for system libraries.
- A data analysis agent that installs what its code imports.
Start
Terminalnpx withruntime sandbox run --trial -- uvx pycowsay hello from uvNew accounts get 50 free sandbox hours, no card. The first run prints a link to approve in your browser.
Sources
- uv documentation: https://docs.astral.sh/uv/, https://docs.astral.sh/uv/pip/, https://docs.astral.sh/uv/pip/environments/, https://docs.astral.sh/uv/concepts/projects/sync/, https://docs.astral.sh/uv/guides/scripts/ and https://docs.astral.sh/uv/guides/tools/, read 25 September 2026.
Facts on this page were checked on 25 September 2026.