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
FargatefargatePrivate on-demand tasks in your AWS account
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 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:

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 editing default. Define a separate ordered app list when apps need different resources:

    bash
    MARIMOHUB_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's compute_profile unless app_compute_profile is 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 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 separate editing and app profiles in Change Compute. The notebook create and update APIs keep compute_profile for editing and add app_compute_profile for apps. A removed choice falls back to the first profile in the corresponding list. Setting a field to null clears 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. Sending default clears an override. For app_compute_profile with a shared list, clearing means the app inherits the editing selection. Set app profiles through the notebook API or Change Compute; MCP create_notebook and 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 declares capabilities.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 ​

  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

Exec, 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 ​

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

  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_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 override

For private named Modal images published with image.publish(), prefix the name with modal://:

bash
MARIMOHUB_COMPUTE_IMAGE=modal://marimo-sandbox:v1

The 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 ​

  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.

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:

  1. Copy packages/compute-fargate/agent/fargate_agent.py into your notebook image. Make the file executable. The agent needs Python 3 and no third-party Python packages.
  2. Register a Linux awsvpc task definition with FARGATE compatibility. Configure a non-root user. Configure /workspace as 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. Set taskRoleArn to 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.
  3. 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.
  4. Give the hub the required ECS permissions. Permit iam:PassRole only for the execution role and task role. See the example policy.
  5. Set these environment variables on the hub:
bash
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:

  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 (services includes update, so a reconnect can reconcile the Service's ports — add it when upgrading). 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 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 uid

MARIMOHUB_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:

bash
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.27

The 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:

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: token

Kubernetes 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 variableTemplate override
MARIMOHUB_COMPUTE_KUBERNETES_SERVICE_ACCOUNTReplaces spec.serviceAccountName.
MARIMOHUB_COMPUTE_KUBERNETES_IMAGE_PULL_SECRETReplaces the entire imagePullSecrets list.
MARIMOHUB_COMPUTE_KUBERNETES_IMAGE_PULL_POLICYReplaces the container pull policy.
MARIMOHUB_COMPUTE_KUBERNETES_RUN_AS_USERReplaces Pod runAsUser, runAsNonRoot, and fsGroup. Preserves other security fields.
MARIMOHUB_COMPUTE_KUBERNETES_POD_LABELSOverrides 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 labels and annotations, including the Pod name and namespace. Reserved management labels and the sandbox identity annotation are also prohibited.
  • Container image, command, args, ports, and resources. MarimoHub supplies these from runtime configuration and compute profiles.
  • Extra containers, initContainers, ephemeralContainers, Pod-level resources, and the deprecated serviceAccount alias.

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:

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 kernel ports on loopback
# MARIMOHUB_COMPUTE_DOCKER_NETWORK=marimo         # optional network to attach kernels to

If 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 ​

  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 kernel ports on loopback
# MARIMOHUB_COMPUTE_PODMAN_NETWORK=marimohub      # optional network to attach kernels to

If 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) ​

  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.

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:

  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.

Warm sandbox pools ​

CoreWeave and Kubernetes support opt-in warm sandbox pools. Pools keep the default image running for faster editor and app startup.

Provider-agnostic. Deploy anywhere.