Skip to content

Compute

Compute is where each notebook's Python kernel runs. The web UI connects to an on-demand sandbox created by the selected compute backend.

See Editor sessions to choose shared or exclusive access to persistent editor sandboxes.

See Session surfaces to attach VS Code or OpenCode to the existing edit sandbox.

Selector: MARIMOHUB_COMPUTE_BACKEND. Full variables: Configuration -> Compute.

Choose a backend

BackendSelectorUse for
CoreWeavecoreweaveProduction on CoreWeave Sandboxes
W&BwandbCoreWeave Sandboxes via your W&B account
ModalmodalProduction serverless sandboxes
E2Be2bManaged code sandboxes
KuberneteskubernetesPods in your own cluster
DockerdockerSingle-host container per kernel
PodmanpodmanRootless or remote container per kernel
LocallocalLocal development with uv run marimo edit
CloudflarecloudflareWorkers entrypoint with Containers binding
NonenoneBrowse notebooks without runnable kernels
ExternallibraryOperator-provided Node adapter

Shared settings

Most production backends need a sandbox image and a public hostname for kernel traffic:

bash
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME=sandboxes.example.net

MARIMOHUB_COMPUTE_IMAGE is the per-kernel container you bring. It also accepts a comma-separated list of images — the first is the default, and the rest are selectable per notebook (for e2b, MARIMOHUB_COMPUTE_E2B_TEMPLATE takes a list of template ids the same way). See Sandbox image for the contract, a pre-warmed example, and how multiple images behave.

The hub reaps idle sessions after MARIMOHUB_SESSION_IDLE_TIMEOUT_SECONDS. MARIMOHUB_SESSION_APP_IDLE_TIMEOUT_SECONDS can override this value for Run as app sessions. The hub saves persistent edit sessions before it stops their sandboxes. Modal uses 1.5 times the effective timeout as a provider fallback.

MARIMOHUB_SANDBOX_EXPOSURE controls how kernels reach the browser:

  • subdomain (default): kernels are served from an isolated kernel domain.
  • proxy: kernel traffic is forwarded through the app origin.

See Security -> Kernel exposure for the trust model.

OpenCode supports only subdomain exposure. Configuration fails when OpenCode and proxy mode are both enabled.

Compute profiles

Operators can define named CPU, memory, and GPU profiles in an ordered list:

bash
MARIMOHUB_COMPUTE_PROFILES="small:cpu=1;mem=2Gi,gpu-large:cpu=8;mem=32Gi;gpu=A100"
# Optional: let editors choose a profile per notebook.
MARIMOHUB_COMPUTE_PROFILE_OVERRIDE="editors"
  • The first profile is the default and applies to every new sandbox. Reordering the list changes the default.
  • Use <type>[:<count>] for GPU values. Examples include A100, T4:2, and A100-80GB:4. The maximum count is 8. The Modal backend applies GPU values when it creates a sandbox. Other backends ignore GPU values and log a startup warning. They continue to apply supported CPU and memory values.
  • Profile names are stable identifiers. Renaming is remove-and-add; CPU, memory, and GPU values under an existing name can be changed freely.
  • Changes apply on the next session start. Running kernels keep their current resources. The session details show both the running and selected profile until the restart.
  • With MARIMOHUB_COMPUTE_PROFILE_OVERRIDE=editors, editors can choose a non-default profile per notebook. The choice falls back to the first profile if that profile is later removed; the stored name is retained in case the operator restores it. A viewer's own ephemeral edit kernel always uses the default, but the shared notebook app runs the notebook's chosen profile regardless of who starts it.
  • A failed non-default personal edit session can be retried once on Default without changing the notebook's stored choice. Shared apps always use the notebook's selected profile.
  • A filesystem snapshot restores with the resources it was captured on. The session details identify snapshot-backed compute until a fresh sandbox is started.
  • Docker, Podman, Kubernetes, Modal, CoreWeave, and W&B apply profiles. E2B, Cloudflare, local, and none ignore them, hide the feature from the UI, and log a startup warning; their existing backend-specific sizing remains unchanged.

Docker and Podman enforce each container's limits but have no admission control. Ensure the host can accommodate the expected concurrency; N concurrent sandboxes at the largest profile can exceed the host's capacity.

Configure it

CoreWeave

  1. Get a CoreWeave Sandbox API key from your CoreWeave account.
  2. Build or pick a sandbox image (marimo + uv + python) and publish it where CoreWeave can pull it.
  3. Set the env and start marimohub:
bash
MARIMOHUB_COMPUTE_BACKEND=coreweave
MARIMOHUB_COMPUTE_COREWEAVE_API_KEY=           # secret
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME=hub.example.com
MARIMOHUB_COMPUTE_COREWEAVE_RUNNER_ID=marimohub    # your sandbox runner's id

Best for CoreWeave deployments

This is the compute half of the CKS deployment — pair it with CAIOS storage. If you're already on CoreWeave, it's the lowest-friction option.

Ingress is runner-specific

The public kernel URL scheme comes from the runner's default policy. If kernels don't connect, set MARIMOHUB_COMPUTE_COREWEAVE_HOSTNAME_TEMPLATE — see the Configuration reference.

Per-sandbox customization uses templates

Profiles and network modes are not per-create settings. Sandboxes run under the runner's default policy unless MARIMOHUB_COMPUTE_COREWEAVE_TEMPLATE_ID selects an org-scoped sandbox template; personal storage uses its own template via …_USER_HOME_TEMPLATE_ID.

W&B

  1. Get a W&B API key from your wandb.ai user settings (optionally note the entity/team and project to attribute sandboxes to).
  2. Build or pick a sandbox image (marimo + uv + python), as for CoreWeave.
  3. Set the env and start marimohub:
bash
MARIMOHUB_COMPUTE_BACKEND=wandb
MARIMOHUB_COMPUTE_WANDB_API_KEY=               # secret
MARIMOHUB_COMPUTE_WANDB_ENTITY=my-team          # optional
MARIMOHUB_COMPUTE_WANDB_PROJECT=my-project      # optional
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest

Same backend as CoreWeave — no hostname config

W&B sandboxes are CoreWeave Sandboxes behind the W&B gateway — same adapter and API; only the credential differs. Kernel URLs are resolved automatically (the managed runner assigns each sandbox its own public IP), so MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME is not needed. See the Configuration reference for all variables.

Gateway limitations

Kernels are served over plain HTTP at a per-sandbox public IP — an HTTPS-served hub will hit mixed-content blocking in the browser, so this backend currently suits local/HTTP deployments. The gateway also doesn't support profile/placement overrides, GPU requests, egress overrides, or automatic CAIOS bucket credentials — for cloud-storage access use hub-minted Workload Identity Federation instead.

  1. Create a Modal account.
  2. In the dashboard, open Settings → API Tokens and create a token (you get a token id and secret).
  3. Build/publish a sandbox image (marimo + uv + python).
  4. Set the env:
bash
MARIMOHUB_COMPUTE_BACKEND=modal
MARIMOHUB_COMPUTE_MODAL_TOKEN_ID=              # secret
MARIMOHUB_COMPUTE_MODAL_TOKEN_SECRET=          # secret
MARIMOHUB_COMPUTE_MODAL_ENVIRONMENT=notebooks   # optional named environment
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_SESSION_IDLE_TIMEOUT_SECONDS=1800     # save and stop after 30 idle minutes
# MARIMOHUB_SESSION_APP_IDLE_TIMEOUT_SECONDS=7200  # optional app override

No infrastructure to run

Modal is fully serverless — nothing to provision or scale, and you pay only for running kernels. The easiest path if you don't already run a cluster.

The adapter uses the supported Modal JavaScript SDK to create and reconnect to sandboxes. When MARIMOHUB_COMPUTE_MODAL_ENVIRONMENT is set, apps and sandboxes are isolated in that Modal environment. It passes compute profiles through the SDK's cpu, memoryMiB, and gpu options. Modal sets its idle timeout to 1.5 times the effective timeout for each session mode. This fallback gives the hub time to save an edit session and stop its sandbox first.

Cold starts & shared workspaces

A freshly-started kernel can take a few seconds to boot; a warm sandbox image (Sandbox image) helps. If multiple apps share one Modal workspace, set MARIMOHUB_COMPUTE_MODAL_APP_NAME so marimohub only reaps its own sandboxes.

E2B

  1. Create an E2B account and grab an API key.
  2. Build an E2B template with marimo + uv + python (or reuse one), and note its template id — examples/e2b-template is a copy-pasteable one (E2B build system v2).
  3. Install the SDK into your server image — it's an optional, bring-your-own dependency: pnpm add e2b.
  4. Set the env:
bash
MARIMOHUB_COMPUTE_BACKEND=e2b
MARIMOHUB_COMPUTE_E2B_API_KEY=                 # secret
MARIMOHUB_COMPUTE_E2B_TEMPLATE=marimo           # E2B template id (falls back to MARIMOHUB_COMPUTE_IMAGE)
# MARIMOHUB_COMPUTE_E2B_MAX_LIFETIME_SECONDS=3600   # hard cap so stray sandboxes auto-kill

Managed sandboxes, direct kernel URLs

Each session gets a sandbox with a public per-port URL (https://<port>-<id>.e2b.app), so the browser talks to the kernel directly — no ingress to configure.

Template ≠ container image

MARIMOHUB_COMPUTE_E2B_TEMPLATE is an E2B template id, not a Docker image. And because the e2b SDK is bring-your-own, you must bake it into the server image or the backend won't load.

Set the kernel's env for a login shell

E2B runs each command in a login shell that re-sources /etc/profile, so a template's build-time ENV/setEnvs does not reach the kernel launch. Put the runtime env the launch needs — above all UV_PROJECT_ENVIRONMENT (so uv run --no-sync marimo resolves your pre-installed venv) — in an /etc/profile.d/*.sh script instead. The examples/e2b-template template does exactly this.

Bundled / edge runtimes (e.g. Cloudflare Workers)

The e2b SDK bundles and runs in the Workers runtime, but a Worker can't lazily import() it. Inject a statically-imported SDK into createE2bClient's loadSdk argument instead of relying on the default dynamic import — examples/cloudflare-worker/src/e2b.ts is a one-liner that does this.

Kubernetes

Run each kernel as a Pod in your own cluster. Before setting the env, make sure the cluster is ready:

  1. Bake the client in: pnpm add @kubernetes/client-node (bring-your-own dependency) and rebuild your server image.
  2. Grant RBAC: marimohub's ServiceAccount needs pods and services in the kernel namespace. Subdomain exposure also needs ingresses.
  3. For subdomain exposure, configure ingress + TLS: an ingress controller, a *.{host} DNS record, and either a matching wildcard TLS secret or an ingress-controller default certificate so each {id}.{host} kernel URL is HTTPS.
  4. Set the env:
bash
MARIMOHUB_COMPUTE_BACKEND=kubernetes
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME=hub.example.com           # kernels at https://<id>.hub.example.com
MARIMOHUB_COMPUTE_KUBERNETES_NAMESPACE=marimo-kernels
MARIMOHUB_COMPUTE_KUBERNETES_INGRESS_CLASS=traefik
MARIMOHUB_COMPUTE_KUBERNETES_TLS_SECRET=marimo-kernels-wildcard-tls
# For OpenShift, replace `traefik` above with the cluster's IngressClass
# (usually `openshift-default`; verify with `oc get ingressclass`), remove or
# comment out the TLS_SECRET line, then uncomment these settings:
# MARIMOHUB_COMPUTE_KUBERNETES_INGRESS_TLS_MODE=controller-default
# MARIMOHUB_COMPUTE_KUBERNETES_INGRESS_ANNOTATIONS='{"route.openshift.io/termination":"edge"}'
# Optional per-kernel resources:
# MARIMOHUB_COMPUTE_KUBERNETES_CPU=2  MARIMOHUB_COMPUTE_KUBERNETES_MEMORY=4Gi  MARIMOHUB_COMPUTE_KUBERNETES_GPU=1
# Optional tuning:
# MARIMOHUB_COMPUTE_KUBERNETES_IMAGE_PULL_POLICY=IfNotPresent  # default: Always for :latest, else IfNotPresent
# MARIMOHUB_COMPUTE_KUBERNETES_POD_READY_TIMEOUT_SECONDS=120

For proxy exposure, omit MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME and the Ingress/TLS settings. marimohub uses the internal Service URL and does not manage Ingresses, so a MARIMOHUB_COMPUTE_KUBERNETES_HOSTNAME_TEMPLATE that uses {host} or {token} is rejected at boot:

bash
MARIMOHUB_COMPUTE_BACKEND=kubernetes
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_KUBERNETES_NAMESPACE=marimo-kernels
MARIMOHUB_SANDBOX_EXPOSURE=proxy
MARIMOHUB_SANDBOX_PROXY_ACK_UNTRUSTED=true

Existing subdomain deployment

Before you select proxy exposure, complete the required session drain. Proxy mode cannot delete an Ingress from an old subdomain session.

See Deploying → Kubernetes for the full RBAC + ingress recipe.

Most control, runs on your own cluster

Best when you already operate Kubernetes and want kernels to stay inside your network with your own resource limits and GPUs.

The most setup of any backend

Subdomain exposure requires ingress, DNS, TLS, and RBAC. For one host without a cluster, use docker. For hosted compute, use modal.

Slow kernel starts?

Pin the image by digest (not :latest) and pre-pull it on kernel nodes — see Startup latency.

Docker

  1. Make sure the server has the docker CLI on its PATH with access to the daemon (the local socket, or a remote DOCKER_HOST).
  2. Pull/publish a sandbox image (marimo + uv + python).
  3. Set the env:
bash
MARIMOHUB_COMPUTE_BACKEND=docker
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_DOCKER_HOST=localhost         # hostname used in the kernel URL
MARIMOHUB_COMPUTE_DOCKER_BIND_HOST=127.0.0.1    # keep tokenless kernel ports on loopback
# MARIMOHUB_COMPUTE_DOCKER_NETWORK=marimo         # optional network to attach kernels to

At boot the server shells out docker info as a preflight: a missing CLI (spawn docker ENOENT) or an unreachable daemon is reported as a non-fatalpreflight_check log line with the fix, before anyone opens a notebook.

For browsers on another machine, keep the loopback binding and use MARIMOHUB_SANDBOX_EXPOSURE=proxy so kernel traffic goes through the hub's authentication and per-session authorization.

Direct kernel exposure

Kernels run without their own authentication token. If you deliberately publish them directly on a trusted, isolated network, set MARIMOHUB_COMPUTE_DOCKER_BIND_HOST=0.0.0.0 and set MARIMOHUB_COMPUTE_DOCKER_HOST to the server hostname browsers can reach. Never expose those ports to the public internet.

Simplest self-hosted option

A container per kernel on one box — no cloud account, no cluster. Good for a single VM or on-prem host: see Deploying on a single instance for the full recipe, or examples/docker-compose for a ready-to-run local stack.

Single host only

There's no cross-host scheduling, so capacity is capped by one machine. For a cluster, use kubernetes.

Podman

  1. Install the podman CLI on the server and configure it for the user running marimohub. Local rootless Podman and remote connections are both supported; confirm access with podman info.
  2. Pull or publish a sandbox image containing marimo, uv, and Python.
  3. Set the environment:
bash
MARIMOHUB_COMPUTE_BACKEND=podman
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_PODMAN_HOST=localhost         # hostname used in the kernel URL
MARIMOHUB_COMPUTE_PODMAN_BIND_HOST=127.0.0.1    # keep tokenless kernel ports on loopback
# MARIMOHUB_COMPUTE_PODMAN_NETWORK=marimohub      # optional network to attach kernels to

For browsers on another machine, keep the loopback binding and use MARIMOHUB_SANDBOX_EXPOSURE=proxy so kernel traffic goes through the hub's authentication and per-session authorization.

Direct kernel exposure

Kernels run without their own authentication token. If you deliberately publish them directly on a trusted, isolated network, set MARIMOHUB_COMPUTE_PODMAN_BIND_HOST=0.0.0.0 and set MARIMOHUB_COMPUTE_PODMAN_HOST to the server hostname browsers can reach. Never expose those ports to the public internet.

The published marimohub server image does not bundle Podman. If the server runs inside a container, provide a Podman remote client and mount/configure the appropriate Podman service socket yourself.

Single host only

Podman creates one container per kernel but does not add cross-host scheduling. Use kubernetes when kernels need cluster scheduling.

Local (dev)

  1. Install uv and Python on the host running marimohub.
  2. Set the env:
bash
MARIMOHUB_COMPUTE_BACKEND=local
# MARIMOHUB_COMPUTE_LOCAL_ROOT=/var/lib/marimohub/sandboxes # keep notebooks outside /tmp
# MARIMOHUB_COMPUTE_LOCAL_HOST=localhost      # host the kernel URL points at
# MARIMOHUB_COMPUTE_LOCAL_BIND_HOST=127.0.0.1 # set 0.0.0.0 when running in Docker
# MARIMOHUB_COMPUTE_LOCAL_PORTS=2718-2723     # published port range (required in Docker)

Fastest way to try marimohub

Spawns uv run marimo edit as a subprocess on the host — nothing to provision. Great for pnpm dev on your laptop.

Configure the sandbox root

marimo treats notebooks under the operating system's temporary directory as temporary files and opens Save As instead of saving them in place. Set MARIMOHUB_COMPUTE_LOCAL_ROOT to a writable directory outside the temporary directory. Each sandbox gets its own child directory beneath this root.

Development only

Kernels run as host subprocesses with no isolation, and it only works when the server runs directly on the host. Never use it for shared or production deployments — pick docker, kubernetes, modal, coreweave, or e2b.

None

No setup — just select it:

bash
MARIMOHUB_COMPUTE_BACKEND=none

Notebooks are browsable, but won't run

Use this to stand up the control plane (storage + auth + UI) before you've wired compute. Starting a kernel fails until you switch to a real backend.

External library

Set the backend and module:

bash
MARIMOHUB_COMPUTE_BACKEND=library
MARIMOHUB_COMPUTE_LIBRARY=/etc/marimohub/compute.mjs

The module must default-export an API version 1 compute manifest. Its factory must return a complete SandboxProvider. The server validates the first sandbox against the SandboxInstance contract.

Only the Node server supports external adapters. Load only trusted code. It runs in-process with server privileges.

Validate it

After deploy:

  1. Create or open a notebook.
  2. Start a kernel session.
  3. Confirm the notebook connects in the browser.
  4. Stop the session.
  5. Confirm the session disappears from active sessions after the configured timeout or cleanup pass.

Production cautions

  • Use an isolated sandbox hostname for subdomain mode.
  • Set resource limits and idle timeouts before inviting real users.
  • Use none only when users should browse notebooks without running kernels.
  • Keep the sandbox image patched. It is part of the runtime security boundary.

Troubleshooting

See Troubleshooting -> Kernels won't start and Sandbox image.

Provider-agnostic. Deploy anywhere.