On Runtime, a notebook runs in its own Firecracker microVM with pandas, NumPy and matplotlib already installed, so a notebook an agent wrote, or one a user uploaded, executes away from your servers and your keys. The sandbox bills the CPU the kernel uses: $0.03125 an hour for 2 vCPU and 4 GiB while cells wait, $0.08 with both CPUs busy (pricing). On 25 October 2026 PyPI's current releases were JupyterLab 4.6.4, Notebook 7.6.3, nbconvert 7.17.2, papermill 2.7.0 and ipykernel 7.4.0.
Three ways to run notebook code
| You have | Use | Needs an install |
|---|---|---|
| Cells an agent writes one at a time | The sandbox's code interpreter | No |
A whole .ipynb to run, maybe with inputs |
papermill or jupyter nbconvert --execute |
pip install one package |
| A person who wants to open the notebook | JupyterLab behind a private preview | pip install jupyterlab |
The interpreter keeps variables between calls and returns charts as PNG and data frames as tables, in Python, JavaScript, TypeScript, R, Java, Bash or Go. The rest of this page covers the other two rows.
Execute a notebook with parameters
papermill runs every cell and writes a new notebook with the outputs. Values
passed with -p go into a cell it inserts after the one tagged parameters.
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 1800 });await sbx.exec("pip install --quiet papermill==2.7.0 nbconvert==7.17.2 ipykernel==7.4.0", { check: true, timeoutMs: 300_000,});await sbx.files.upload("./report.ipynb", "/workspace/report.ipynb");await sbx.files.upload("./sales.csv", "/workspace/sales.csv");const run = await sbx.exec( "mkdir -p out && papermill report.ipynb out/report.ipynb -p region west -p year 2026", { timeoutMs: 1_200_000, onStderr: (text) => process.stderr.write(text) },);await sbx.exec("jupyter nbconvert --to html out/report.ipynb", { check: true, timeoutMs: 120_000 });await sbx.files.download("/workspace/out", "./out");process.exitCode = run.exitCode ?? 1;Pythonimport sysfrom withruntime import Sandboxwith Sandbox.create(timeout_seconds=1800) as sbx: sbx.exec("pip install --quiet papermill==2.7.0 nbconvert==7.17.2 ipykernel==7.4.0", check=True, timeout_ms=300_000) sbx.files.upload("report.ipynb", "/workspace/report.ipynb") sbx.files.upload("sales.csv", "/workspace/sales.csv") run = sbx.exec("mkdir -p out && papermill report.ipynb out/report.ipynb -p region west -p year 2026", timeout_ms=1_200_000, on_stderr=sys.stderr.write) sbx.exec("jupyter nbconvert --to html out/report.ipynb", check=True, timeout_ms=120_000) sbx.files.download("/workspace/out", "out") print("exit", run.exit_code)The exit code says whether every cell ran. The out folder comes back in one
call: the executed .ipynb, its HTML rendering, and any file a cell saved
there. Progress streams to your terminal while it runs.
For a notebook with no parameters, nbconvert alone executes it:
Terminaljupyter nbconvert --to notebook --execute report.ipynb --output report.done.ipynb \ --ExecutePreprocessor.timeout=600nbconvert's documentation sets its per-cell timeout at 30 seconds by default;
-1 removes it. Set the sandbox's own timeoutMs above the whole run either
way, since a command's default is 60 seconds.
Open JupyterLab in your browser
Start the server with spawn so it keeps running. Then share its port as a
preview. A preview is private: a browser or a script needs its link, which
carries the token, or the token header. That link is the lock here, so
Jupyter's own token is turned off.
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 3600, idlePauseSeconds: 900 });await sbx.exec("pip install --quiet jupyterlab==4.6.4", { check: true, timeoutMs: 300_000 });await sbx.spawn( "jupyter lab --no-browser --ip=127.0.0.1 --port=8888 " + "--IdentityProvider.token='' --ServerApp.allow_remote_access=True",);const preview = await sbx.previews.create(8888);console.log("open:", preview.urlWithToken);Pythonfrom withruntime import Sandboxsbx = Sandbox.create(timeout_seconds=3600, idle_pause_seconds=900)sbx.exec("pip install --quiet jupyterlab==4.6.4", check=True, timeout_ms=300_000)sbx.spawn("jupyter lab --no-browser --ip=127.0.0.1 --port=8888 " "--IdentityProvider.token='' --ServerApp.allow_remote_access=True")preview = sbx.previews.create(8888)print("open:", preview["urlWithToken"])allow_remote_accesslets Jupyter answer requests whoseHostheader is the preview's address rather thanlocalhost, as Jupyter Server's options describe.- Kernels talk over WebSockets, which previews carry.
- With
idlePauseSeconds, the sandbox pauses after 15 quiet minutes. A pause keeps memory and processes. The kernel and its variables are still there when you come back. The next visit wakes the sandbox with a short "Waking up" page (pause and resume).
To use the notebook from VS Code instead, connect over SSH to
<id>.runtime and run Jupyter there (SSH and editors).
Start every sandbox with Jupyter ready
Build a custom image that installs the packages once:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "notebooks", recipe: { pip: ["jupyterlab==4.6.4", "papermill==2.7.0", "scikit-learn"] },});await using sbx = await runtime.sandboxes.create({ image: "notebooks" });Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="notebooks", recipe={"pip": ["jupyterlab==4.6.4", "papermill==2.7.0", "scikit-learn"]})sbx = runtime.sandboxes.create(image="notebooks")Building is free. An account without credit stores three images free. After that, an image is charged by its size.
Keep an untrusted notebook contained
- No internet: create the sandbox with
network: { internet: false }after packages are installed or baked into an image. A notebook then cannot send data anywhere (turn off sandbox internet). - No keys inside: pass what a notebook needs as
envon the command, or store API keys as secrets the sandbox never sees (security). - Memory: a sandbox gets the memory you request with
memoryMiB. Without credit, the limit is up to 4 GiB. - Disk: the default 4 GiB disk had about 2.5 GiB free on 24 September 2026.
Large datasets need a larger
diskMiB, or a volume that outlives the sandbox.
For an agent that answers questions about uploaded data, see a data analysis agent; for charts and tables in a chat product, see a code interpreter for chatbots.
Sources
- papermill, execute and parameterize, https://papermill.readthedocs.io/en/latest/usage-execute.html and https://papermill.readthedocs.io/en/latest/usage-parameterize.html, read 25 October 2026
- nbconvert, executing notebooks, https://nbconvert.readthedocs.io/en/latest/execute_api.html, read 25 October 2026
- Jupyter Server configuration, https://jupyter-server.readthedocs.io/en/latest/other/full-config.html, read 25 October 2026
- PyPI project pages for jupyterlab, notebook, nbconvert, papermill and ipykernel, https://pypi.org/, read 25 October 2026
Facts on this page were checked on 25 October 2026.