Skip to content
Runtime

How to run Jupyter notebooks headless in the cloud

Upload the notebook to a sandbox. Run it with papermill or jupyter nbconvert --execute. Then 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 less than a cent per run. 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. They write a new notebook that contains every output. A scheduled job or CI pipeline needs this. The interpreter suits code that picks the next cell after it sees the output of the last one, such as an agent.

Build an image with Jupyter

The default image has Python 3.12, pandas 2.1.4, NumPy 1.26.4 and matplotlib 3.6.3. Add the notebook tools and every package 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 an image costs nothing. Every run starts with those packages already installed (custom images).

Run a parameterised notebook with papermill

papermill reads the cell tagged parameters. It inserts a new cell tagged injected-parameters right after it. That cell holds the values you pass with -p (papermill's documentation, read 26 October 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 that a cell raised an error. Keep the last part of stderr for the alert. Keep the output notebook for the person who fixes the error.

Or run it as it is with nbconvert

Terminaljupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=600 analysis.ipynb

By default, nbconvert gives each cell 30 seconds and stops at the first error. --ExecutePreprocessor.timeout raises the time limit. allow_errors runs the notebook to the end even after an error (nbconvert's documentation, read 26 October 2026). Put the command in an exec call, as in the papermill example.

Many parameter sets at once

A report for each region, customer or model checkpoint uses the same notebook with different parameters. Give each run its own sandbox, and run them together. A paid account runs 100 sandboxes at the same time. A create request beyond that limit 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 are uploaded with the notebook, using files.upload.
  • A bucket mounts as a directory. The host's proxy holds the access key, not 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. The proxy replaces the placeholder with 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; the sandbox has no time limit
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 is baked into the image
Someone else's notebook A microVM of its own; network limited or turned 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, which is 900 runs a month. Each run keeps a 2 vCPU, 4 GiB sandbox for 10 minutes. Its cells use 6 CPU-minutes (360 CPU-seconds):

CPU
900 × 360 s / 3,600 × $0.025
$2.25
Memory
900 × 600 s / 3,600 × 4 GiB × $0.0075
$4.50
Total
$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). Nothing runs between runs, so nothing is billed. A new account gets 100 free hours of a 1 GB machine in its first 30 days, with 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 26 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card