Runtime

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.ipynb

nbconvert 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.75

That 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

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.