How to run Jupyter notebooks headless in the cloud
Upload the notebook to a sandbox, run it with papermill or jupyter nbconvert --execute, and download the executed copy.
On Runtime a notebook run pays for the CPU its cells use and for memory only while it runs. A nightly report that keeps a 2 vCPU, 4 GiB sandbox for ten minutes costs under a cent a run, and a sandbox is ready for its first Python command in 351 ms at the median, measured on 24 September 2026 (speed). No server sits idle between runs.
Two ways to run a notebook
| Approach | Best for | What you get back |
|---|---|---|
papermill on the .ipynb file |
Scheduled reports with parameters | An executed notebook, one per parameter set |
jupyter nbconvert --execute |
Running a notebook as it is | An executed notebook |
| Runtime's code interpreter | A program or agent sending one cell at a time | Each cell's text, tables and PNG charts |
papermill and nbconvert run the whole file and write a new notebook with every output in it, which is what a scheduled job or CI wants. The interpreter suits code that decides the next cell after seeing the last one, such as an agent.
Build an image with Jupyter
The default image has Python 3.12 with pandas 2.1.4, NumPy 1.26.4 and matplotlib 3.6.3. Add the notebook tools and whatever your notebooks import:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "notebooks", recipe: { pip: ["papermill", "nbconvert", "ipykernel", "scikit-learn"] },});Building costs nothing, and the image starts every run with those packages already installed (custom images).
Run a parameterised notebook with papermill
papermill reads the cell tagged parameters and inserts a new cell tagged
injected-parameters right after it, holding the values you pass with -p
(papermill's documentation, read 25 September 2026).
TypeScriptimport { writeFile } from "node:fs/promises";import { Runtime } from "withruntime";const runtime = new Runtime();await using sbx = await runtime.sandboxes.create({ image: "notebooks", timeoutSeconds: 1800, onLeaseEnd: "stop", labels: { job: "weekly-report" },});await sbx.files.upload("./reports", "/workspace/reports");const run = await sbx.exec( ["papermill", "sales.ipynb", "out/sales-west.ipynb", "-p", "region", "west", "-p", "weeks", "12"], { cwd: "/workspace/reports", timeoutMs: 1_500_000 },);if (run.exitCode !== 0) console.error(run.stderr.slice(-2000)); // the failing cell's tracebackawait writeFile( "sales-west.ipynb", await sbx.files.read("/workspace/reports/out/sales-west.ipynb"),);Pythonfrom withruntime import Runtimeruntime = Runtime()with runtime.sandboxes.create( image="notebooks", timeout_seconds=1800, on_lease_end="stop", labels={"job": "weekly-report"},) as sbx: sbx.files.upload("./reports", "/workspace/reports") run = sbx.exec( ["papermill", "sales.ipynb", "out/sales-west.ipynb", "-p", "region", "west", "-p", "weeks", "12"], cwd="/workspace/reports", timeout_ms=1_500_000, ) if run.exit_code != 0: print(run.stderr[-2000:]) # the failing cell's traceback with open("sales-west.ipynb", "wb") as target: target.write(sbx.files.read("/workspace/reports/out/sales-west.ipynb"))A non-zero exit code means a cell raised. Keep the tail of stderr for the
alert, and the output notebook for whoever fixes it.
Or run it as it is with nbconvert
Terminaljupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=600 analysis.ipynbnbconvert gives each cell 30 seconds by default and stops at the first error;
--ExecutePreprocessor.timeout raises the limit, and allow_errors runs the
notebook to the end regardless (nbconvert's documentation, read 25 September
2026). Put the command in an exec exactly as in the papermill example.
Many parameter sets at once
A report per region, per customer or per model checkpoint is the same notebook with different parameters. Give each its own sandbox and run them together; a paid account starts with room for 100 at once, and a create beyond that waits for a free slot instead of failing:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime({ waitForCapacityMs: 600_000 });const regions = ["west", "east", "north", "south"];await Promise.all( regions.map(async (region) => { await using sbx = await runtime.sandboxes.create({ image: "notebooks", labels: { region } }); await sbx.files.upload("./reports", "/workspace/reports"); await sbx.exec(["papermill", "sales.ipynb", `out/${region}.ipynb`, "-p", "region", region], { cwd: "/workspace/reports", timeoutMs: 1_500_000, }); await sbx.files.download(`/workspace/reports/out/${region}.ipynb`, `./out/${region}.ipynb`); }),);Data the notebook reads
- Small inputs go up with the notebook in
files.upload. - A bucket mounts as a directory, with the access key held by the host's proxy rather than the sandbox (mount your own bucket).
- A warehouse or API key is stored as a Runtime secret. The notebook reads an environment variable that holds a placeholder, and the proxy swaps in the real value only on HTTPS requests to the hosts you named (secrets).
What notebook jobs need
| Need | How Runtime covers it |
|---|---|
| The same environment every run | A versioned image; notebooks@4 pins one build |
| A notebook that runs for hours | Commands up to 24 hours; extend or keepAlive moves the lease |
| Charts | Saved in the executed .ipynb; the interpreter returns them as PNG |
| R code | The interpreter runs R cells; R installs on first use, or bake it in |
| Someone else's notebook | A microVM of its own; network narrowed or off |
| Knowing a run failed | A sandbox.stopped webhook, or the exit code in your scheduler |
What it costs
A team runs 30 notebooks every night: 900 runs a month. Each keeps a 2 vCPU, 4 GiB sandbox for 10 minutes, and its cells use 6 CPU-minutes (360 CPU-seconds):
TextCPU: 900 × 360 s / 3,600 × $0.025 = $2.25Memory: 900 × 600 s / 3,600 × 4 GiB × $0.0075 = $4.50Total: $6.75That is 0.75 cents a run, at $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour (pricing). Between runs nothing is running, so nothing is billed. 50 sandbox hours come free on a new account, no card needed.
Sources
- papermill: execute
and parameterize:
-p, theparameterstag andinjected-parameters, read 25 September 2026. - nbconvert: executing notebooks:
--execute, the 30-second cell timeout andallow_errors, read 25 September 2026.
Related: data analysis agent, what a code interpreter is, code interpreter for chatbots, Python in a sandbox.
Facts on this page were checked on 25 September 2026.