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
| Backend | Selector | Use for |
|---|---|---|
| CoreWeave | coreweave | Production on CoreWeave Sandboxes |
| W&B | wandb | CoreWeave Sandboxes via your W&B account |
| Modal | modal | Production serverless sandboxes |
| E2B | e2b | Managed code sandboxes |
| Fargate | fargate | Private on-demand tasks in your AWS account |
| Kubernetes | kubernetes | Pods in your own cluster |
| Docker | docker | Single-host container per kernel |
| Podman | podman | Rootless or remote container per kernel |
| Local | local | Local development with uv run marimo edit |
| Cloudflare | cloudflare | Workers entrypoint with Containers binding |
| None | none | Browse notebooks without runnable kernels |
| External | library | Operator-provided Node adapter |
Shared settings
Most production backends need a sandbox image and a public hostname for kernel traffic:
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME=sandboxes.example.netMARIMOHUB_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 Fargate task definition sets the image. The backend rejects MARIMOHUB_COMPUTE_IMAGE and supports proxy exposure only.
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:
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 editing default. Define a separate ordered app list when apps need different resources:
bashMARIMOHUB_COMPUTE_PROFILES="editing:cpu=2;mem=4Gi" MARIMOHUB_COMPUTE_APP_PROFILES="app:cpu=1;mem=2Gi"The first app profile is the app default, even when notebook overrides are disabled. If the app list is unset or empty, apps share the editing list and use the editing default. With that shared list and
MARIMOHUB_COMPUTE_PROFILE_OVERRIDE=editors, apps instead inherit the notebook'scompute_profileunlessapp_compute_profileis set. With overrides disabled, both stored notebook selections are ignored. Names are scoped to each list, so the same name can specify different resources. Kubernetes profiles set both requests and limits; include their full requests in capacity and namespace quota planning.Use
<type>[:<count>]for GPU values. Examples includeA100,T4:2, andA100-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 separate editing and app profiles in Change Compute. The notebook create and update APIs keepcompute_profilefor editing and addapp_compute_profilefor apps. A removed choice falls back to the first profile in the corresponding list. Setting a field tonullclears its override; omitting it in an update preserves its current value. With a separate app list, an unset app override uses the app default regardless of the editing selection. A viewer's ephemeral edit kernel always uses the editing default. Shared apps use the notebook's app selection regardless of who starts them. Sendingdefaultclears an override. Forapp_compute_profilewith a shared list, clearing means the app inherits the editing selection. Set app profiles through the notebook API or Change Compute; MCPcreate_notebookand notebook import set only the editing profile.Notebook previews retain their preview-specific profile policy, and scheduled jobs continue to use the legacy notebook profile and deployment default.
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. Fargate applies CPU and memory profiles and rejects GPU values before launch. E2B, Cloudflare, local, and none ignore profiles and hide the feature from the UI.
An external library adapter applies CPU and memory profiles when it declares
capabilities.computeProfiles, and GPU profiles when it declarescapabilities.gpuProfiles. Otherwise it ignores profiles. See external adapter libraries.
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
- Get a CoreWeave Sandbox API key from your CoreWeave account.
- Build or pick a sandbox image (marimo + uv + python) and publish it where CoreWeave can pull it.
- Set the env and start marimohub:
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 idExec, logs, and file transfers prefer direct runner connections with mTLS. The default MARIMOHUB_COMPUTE_COREWEAVE_DATA_PLANE_MODE=auto allows Gateway fallback. Set this variable to direct to require direct connections, or gateway to disable them. Lifecycle calls still use the API.
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
- Get a W&B API key from your wandb.ai user settings (optionally note the entity/team and project to attribute sandboxes to).
- Build or pick a sandbox image (marimo + uv + python), as for CoreWeave.
- Set the env and start marimohub:
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:latestSame 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 kernel its own HTTPS endpoint), so MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME is not needed. See the Configuration reference for all variables.
Gateway limitations
The gateway doesn't support CoreWeave sandbox profile/placement overrides, GPU requests, egress overrides, or automatic CAIOS bucket credentials. For cloud-storage access, use hub-minted Workload Identity Federation instead.
marimohub requests CoreWeave's maximum 900-second endpoint request timeout. See CoreWeave endpoint timeouts. The gateway's timeout behavior for upgraded WebSockets remains unverified.
Upgrading from 0.4.11 or earlier: sandboxes created before HTTPS endpoints were enabled retain their plain HTTP URLs. The adapter rejects those URLs. Save the notebook files, stop the old session, and start a new session to create an HTTPS endpoint.
The kernel endpoint is reachable from the internet and does not require a gateway credential. Set MARIMOHUB_SANDBOX_AUTH=on so each kernel requires a marimo token. See Native kernel authentication.
Modal
- Create a Modal account.
- In the dashboard, open Settings → API Tokens and create a token (you get a token id and secret).
- Build/publish a sandbox image (marimo + uv + python).
- Set the env:
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_MODAL_SECRETS=shared-credentials,huggingface
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 overrideFor private named Modal images published with image.publish(), prefix the name with modal://:
MARIMOHUB_COMPUTE_IMAGE=modal://marimo-sandbox:v1The tag defaults to latest. Unqualified names resolve in MARIMOHUB_COMPUTE_MODAL_ENVIRONMENT, or the workspace default when unset. A name that contains /, such as modal://my-workspace/notebooks/marimo-sandbox:v1, names its own namespace and ignores that variable. The configured token must have access to the image. Registry images and named Modal images can share a comma-separated image list.
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.
MARIMOHUB_COMPUTE_MODAL_SECRETS accepts comma-separated Modal secret names, not secret values. Every new editor, app, and job sandbox receives them as environment variables across the deployment. The secrets must exist in MARIMOHUB_COMPUTE_MODAL_ENVIRONMENT, or the workspace default when unset. An empty or unset list adds none.
Any secret lookup failure prevents sandbox creation. The error names the secret, the variable, and the Modal environment. The boot preflight also resolves each secret, so it reports a misspelled name at startup. Existing sandboxes retain their secrets.
Deployment-wide secrets
marimohub injects these secrets into every editor, app, and job sandbox in every project. Any notebook author can read the values. For credentials that belong to one project, use project integration secret references instead. Secret keys must avoid hub-reserved names. A MARIMO_* value set here takes precedence over the same variable in a project integration.
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
- Create an E2B account and grab an API key.
- Build an E2B template with marimo + uv + python (or reuse one), and note its template id —
examples/e2b-templateis a copy-pasteable one (E2B build system v2). - Install the SDK into your server image — it's an optional, bring-your-own dependency:
pnpm add e2b. - Set the env:
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-killManaged 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.
AWS ECS Fargate
Fargate runs one on-demand ECS task for each notebook sandbox in your AWS account and VPC. The hub connects to the private task ENI. The browser connects through the authenticated hub proxy.
Before you configure the hub:
- Copy
packages/compute-fargate/agent/fargate_agent.pyinto your notebook image. Make the file executable. The agent needs Python 3 and no third-party Python packages. - Register a Linux
awsvpctask definition withFARGATEcompatibility. Configure a non-root user. Configure/workspaceas a writable directory. Expose ports 2717 and 2718. If you enable VS Code, expose port 8443 by default. If you enable OpenCode, expose port 4096 by default. SettaskRoleArnto the IAM role for notebook AWS access. Do not put static AWS access keys in the image or task definition. See the example task definition. - Put the hub and notebook tasks in private subnets. Allow the hub security group to reach ports 2717 and 2718 on the notebook security group. If you enable VS Code, allow its configured port. If you enable OpenCode, allow its configured port.
- Give the hub the required ECS permissions. Permit
iam:PassRoleonly for the execution role and task role. See the example policy. - Set these environment variables on the hub:
MARIMOHUB_COMPUTE_BACKEND=fargate
MARIMOHUB_SANDBOX_EXPOSURE=proxy
MARIMOHUB_COMPUTE_FARGATE_CLUSTER=marimohub
MARIMOHUB_COMPUTE_FARGATE_TASK_DEFINITION=marimohub-kernel:1
MARIMOHUB_COMPUTE_FARGATE_SUBNETS=subnet-aaa,subnet-bbb
MARIMOHUB_COMPUTE_FARGATE_SECURITY_GROUPS=sg-kernels
MARIMOHUB_COMPUTE_FARGATE_OWNER=prod-hub-a
MARIMOHUB_COMPUTE_FARGATE_AGENT_SECRET='<at least 32 random bytes>'The task definition pins the notebook image. A new task-definition revision changes the image. Do not set MARIMOHUB_COMPUTE_IMAGE for this backend.
MARIMOHUB_COMPUTE_FARGATE_CONTAINER_NAME defaults to marimo. The agent port defaults to 2717. The ready timeout defaults to 120 seconds. Public IP assignment defaults to false.
The owner value must be unique for each independent hub deployment in an AWS account. The adapter uses this value to find and stop its tasks.
The adapter uses the hub runtime's AWS identity to call ECS. It does not have a Fargate-specific static credential setting. When the hub runs outside an AWS-managed runtime, set AWS_REGION.
Fargate maps CPU and memory profiles to valid billed pairs. It does not support GPU profiles, Spot capacity, public subdomains, EFS, or ECS Exec.
Kubernetes
Run each kernel as a Pod in your own cluster. Before setting the env, make sure the cluster is ready:
- Bake the client in:
pnpm add @kubernetes/client-node(bring-your-own dependency) and rebuild your server image. - Grant RBAC: marimohub's ServiceAccount needs
podsandservicesin the kernel namespace (servicesincludesupdate, so a reconnect can reconcile the Service's ports — add it when upgrading). Subdomain exposure also needsingresses. - 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. - Set the env:
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 clusters whose admission policies require ownership labels or a pinned uid:
# MARIMOHUB_COMPUTE_KUBERNETES_POD_LABELS=team=data,app.kubernetes.io/part-of=marimohub
# MARIMOHUB_COMPUTE_KUBERNETES_RUN_AS_USER=1000 # also sets runAsNonRoot and fsGroup; the image workdir must be writable by this uidMARIMOHUB_COMPUTE_KUBERNETES_POD_LABELS adds labels to Pods, Services, and Ingresses. Kubernetes label syntax applies, including empty values such as team=. The hub's management and selector labels take precedence.
MARIMOHUB_COMPUTE_KUBERNETES_RUN_AS_USER sets the Pod UID and matching fsGroup, with runAsNonRoot: true except for UID 0. If unset, the hub uses the template security context or leaves it unset. The UID must have write access to the image work directory. fsGroup affects mounted volumes only.
Pod templates
Use a partial Pod manifest to configure volumes, environment variables, security contexts, and scheduling. Mount the file into the hub server, for example through a ConfigMap. Set its path, the kernel namespace, and the image:
MARIMOHUB_COMPUTE_KUBERNETES_POD_TEMPLATE_FILE=/etc/marimohub/kernel-pod.yaml
MARIMOHUB_COMPUTE_KUBERNETES_NAMESPACE=fabric-marimohub-kernels
MARIMOHUB_COMPUTE_IMAGE=registry.example.com/fabric/runtime:1.0.27The server reads the file once at startup. After a file change, restart the server. The updated template applies only to new Pods.
Example kernel-pod.yaml:
apiVersion: v1
kind: Pod
spec:
serviceAccountName: fabric-marimohub-kernel
automountServiceAccountToken: false
enableServiceLinks: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: marimo
env:
- name: FABRIC_URL
value: https://fabric-gateway.example.com
- name: FABRIC_TOKEN_PATH
value: /var/run/secrets/marimohub/token
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ['ALL']
volumeMounts:
- name: gateway-token
mountPath: /var/run/secrets/marimohub
readOnly: true
volumes:
- name: gateway-token
projected:
defaultMode: 0444
sources:
- serviceAccountToken:
audience: fabric-gateway
expirationSeconds: 600
path: tokenKubernetes rotates the projected token. The application must reread the file after rotation. subPath mounts do not receive updates.
Format. Use one YAML 1.1 or JSON object. YAML mode 0444 equals JSON mode 292. Quote strings such as "true", "yes", and "123" in environment variables, labels, and annotations. All template sections are optional. If supplied, apiVersion must be v1, kind must be Pod, and containers must contain one entry named marimo.
Precedence: explicit environment configuration > template > adapter defaults. Lists remain intact unless explicitly replaced. The hub does not deep-merge templates. Template labels and annotations apply only to Pods.
| Environment variable | Template override |
|---|---|
MARIMOHUB_COMPUTE_KUBERNETES_SERVICE_ACCOUNT | Replaces spec.serviceAccountName. |
MARIMOHUB_COMPUTE_KUBERNETES_IMAGE_PULL_SECRET | Replaces the entire imagePullSecrets list. |
MARIMOHUB_COMPUTE_KUBERNETES_IMAGE_PULL_POLICY | Replaces the container pull policy. |
MARIMOHUB_COMPUTE_KUBERNETES_RUN_AS_USER | Replaces Pod runAsUser, runAsNonRoot, and fsGroup. Preserves other security fields. |
MARIMOHUB_COMPUTE_KUBERNETES_POD_LABELS | Overrides matching labels. Also applies to Services and Ingresses. |
Container security fields take precedence over Pod security fields.
Managed fields. Templates must omit:
- Metadata other than
labelsandannotations, including the Pod name and namespace. Reserved management labels and the sandbox identity annotation are also prohibited. - Container
image,command,args,ports, andresources. MarimoHub supplies these from runtime configuration and compute profiles. - Extra containers,
initContainers,ephemeralContainers, Pod-levelresources, and the deprecatedserviceAccountalias.
restartPolicy must be absent or Never. Other supported fields include nodeSelector, tolerations, affinity, envFrom, and terminationGracePeriodSeconds.
Compatibility. At startup, the hub checks common field shapes, duplicate names, and volume references. Some nested objects reject unfamiliar fields. The Kubernetes SDK can silently drop other unfamiliar fields before submission. Custom labels and annotations are supported. Kubernetes performs full schema and admission checks on the submitted Pod.
Proxy exposure
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:
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=trueExisting 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
- Make sure the server has the
dockerCLI on itsPATHwith access to the daemon (the local socket, or a remoteDOCKER_HOST). - Pull/publish a sandbox image (marimo + uv + python).
- Set the env:
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 kernel ports on loopback
# MARIMOHUB_COMPUTE_DOCKER_NETWORK=marimo # optional network to attach kernels toIf hubs share a Docker daemon, set a distinct, stable MARIMOHUB_COMPUTE_DOCKER_OWNER_TAG on every hub. An untagged hub discovers and cleans up every hub's sandboxes. See Multiple hubs on one host for examples and migration steps.
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
Native kernel authentication is off by default. For direct access 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
- Install the
podmanCLI on the server and configure it for the user running marimohub. Local rootless Podman and remote connections are both supported; confirm access withpodman info. - Pull or publish a sandbox image containing marimo, uv, and Python.
- Set the environment:
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 kernel ports on loopback
# MARIMOHUB_COMPUTE_PODMAN_NETWORK=marimohub # optional network to attach kernels toIf hubs share a Podman container store, set a distinct, stable MARIMOHUB_COMPUTE_PODMAN_OWNER_TAG on every hub. An untagged hub discovers and cleans up every hub's sandboxes. See Multiple hubs on one host for examples and migration steps.
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
Native kernel authentication is off by default. For direct access 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)
- Install
uvand Python on the host running marimohub. - Set the env:
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:
MARIMOHUB_COMPUTE_BACKEND=noneNotebooks 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:
MARIMOHUB_COMPUTE_BACKEND=library
MARIMOHUB_COMPUTE_LIBRARY=/etc/marimohub/compute.mjsThe 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.
The adapter applies compute profiles only when its provider declares them. Set capabilities.computeProfiles: true for CPU and memory, and capabilities.gpuProfiles: true for GPU requests. Each capability flag must be a boolean, or startup fails. See external adapter libraries.
Only the Node server supports external adapters. Load only trusted code. It runs in-process with server privileges.
Validate it
After deploy:
- Create or open a notebook.
- Start a kernel session.
- Confirm the notebook connects in the browser.
- Stop the session.
- Confirm the session disappears from active sessions after the configured timeout or cleanup pass.
Production cautions
- Use an isolated sandbox hostname for
subdomainmode. - Set resource limits and idle timeouts before inviting real users.
- Use
noneonly 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.
Warm sandbox pools
CoreWeave and Kubernetes support opt-in warm sandbox pools. Pools keep the default image running for faster editor and app startup.