Skip to content

Sandbox image (bring your own container) ​

A compute backend decides where a kernel runs (docker, podman, kubernetes, modal, coreweave). The sandbox image decides what runs inside it — the Python version, system libraries, and pre-installed packages your notebooks get.

You bring your own image. Set it once per deployment:

bash
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest

Every container-based backend reads this same variable. (local runs uv on the host and needs no image; e2b uses an E2B template instead — see Compute.)

The repository image also publishes optional session surface targets:

TargetPublished tag suffixIncluded tools
vscode-vscodecode-server and pinned extensions
opencode-opencodeOpenCode, ripgrep, jq, and marimo-pair
tools-toolsVS Code plus the OpenCode tools

OpenCode is pinned to 1.18.17 with separate SHA-256 checksums for Linux x64 and arm64 builds.

Build requirement: All images/marimo-sandbox targets require the notebook_bridge named build context. Older build commands without this context fail. See Build the maintained image.

Multiple images ​

To offer more than one image — say a lean default plus a GPU or heavy-ML variant — set MARIMOHUB_COMPUTE_IMAGE to a comma-separated list:

bash
MARIMOHUB_COMPUTE_IMAGE=ghcr.io/orgname/marimo-sandbox:latest,ghcr.io/orgname/marimo-gpu:latest
  • The first image is the default. Every notebook uses it unless it chooses otherwise.
  • Notebooks pick per-notebook. The create-notebook dialog and the notebook menu's Change base image action offer "Default" plus each listed image. Only a non-default choice is stored (in the notebook's metadata); "Default" always follows the first image, even if you later reorder the list.
  • Changes apply on the next session start. A running kernel keeps the image it booted from; stop and restart the session to switch.
  • Removing an image from the list is safe. A notebook still pointing at it falls back to the default at session start (with a server-side warning), and its stored choice can be updated to any currently-listed image.
  • The full list is served to clients via GET /api/v1/capabilities (sandbox_images); GET /api/v1/version reports only the default.

For the e2b backend the same applies to MARIMOHUB_COMPUTE_E2B_TEMPLATE, which takes a comma-separated list of template ids.

With CoreWeave filesystem snapshots enabled, a notebook restoring from a snapshot boots the snapshot's image — a base-image change takes effect once no restore pointer exists (e.g. after the snapshot is dropped).

The contract ​

marimohub copies the notebook into the image and launches the kernel itself, reusing the image's pre-installed environment. With cwd /workspace:

sh
uv sync --inexact --no-install-package marimo --no-compile-bytecode   # add the notebook's deps (skipped when it declares none)
uv run --no-sync marimo --quiet edit notebook.py --headless --token --token-password-file /tmp/.marimohub-kernel-token --host 0.0.0.0 --port 2718

The provisioner creates the password file after it writes other session credentials. The file is outside /workspace, so workspace snapshots cannot capture it. Custom images must use a marimo version that supports --token-password-file.

During the sync, --no-install-package marimo keeps the image's pinned marimo version even if the notebook declares another version.

Inline dependencies ​

For local and synced notebooks, marimohub installs PEP 723 dependencies after project sync, before the edit session, app session, or job starts. Apps and jobs use their selected source version.

sh
[ -d "${UV_PROJECT_ENVIRONMENT:-.venv}" ] || uv venv "${UV_PROJECT_ENVIRONMENT:-.venv}"
uv export --script notebook.py --format requirements-txt --no-hashes --prune marimo -o "${UV_PROJECT_ENVIRONMENT:-.venv}/marimohub-script-requirements.txt"
uv pip install --python "${UV_PROJECT_ENVIRONMENT:-.venv}" -r "${UV_PROJECT_ENVIRONMENT:-.venv}/marimohub-script-requirements.txt"

Setup failures stop startup with PYTHON_ENV_SETUP_FAILED. The export excludes marimo and packages used only by marimo, preserving the image's marimo version. Direct notebook dependencies remain in the export.

Project and inline requirements resolve separately. Inline pins can replace project versions and leave project requirements unsatisfied. Keep requirements compatible across both files. Headers remain unchanged and are not copied into pyproject.toml. Dependency changes apply at the next sandbox startup.

Installation reuses the image environment, without marimo --sandbox isolation. Inline requires-python does not select the kernel Python. Use an image with a compatible Python version.

Custom indexes must be available to both export and installation. Configure them for uv pip install through sandbox uv configuration, not only in the header.

Image requirements ​

So your image must provide:

  1. uv on PATH — the launch command is uv sync + uv run ….
  2. marimo + your common libraries pre-installed into the project environment (UV_PROJECT_ENVIRONMENT, also activated as VIRTUAL_ENV), at a pinned marimo version. This is what makes the base case start instantly — no per-launch install.
  3. A POSIX shell (sh) + coreutils — base64, mkdir, rm, cat, true. marimohub transfers notebook files and probes reachability with these.
  4. git — only if notebooks use git checkouts, but cheap to include.
  5. A writable working directory (/workspace) and a writable parent for UV_PROJECT_ENVIRONMENT. uv can replace the whole environment to change Python versions. If the user cannot write to /workspace, set MARIMOHUB_COMPUTE_WORKDIR to a writable directory.
  6. PyPI egress at runtime for notebooks that use libraries beyond the pre-installed base.

No marimo entrypoint or CMD is required — marimohub supplies the launch command. The repository images use a generated token in their quiet default CMD. For a standalone check, publish the port and read the token from the container:

sh
cid=$(docker run -d -p 127.0.0.1:2718:2718 IMAGE)
token=$(docker exec "$cid" cat /tmp/.marimohub-kernel-token)
printf 'http://127.0.0.1:2718/?access_token=%s\n' "$token"

Don't set UV_COMPILE_BYTECODE in your image. Compile bytecode at build with the --compile-bytecode flag (fast imports for the pre-installed base). The startup sync passes --no-compile-bytecode to skip the ~5s of compiling freshly added deps on the launch path — and uv errors if both the env var and the flag are set.

Configure source builds ​

Source builds are allowed by default. UV_NO_BUILD controls both project sync and inline dependency installation.

To disable source builds, add this line after dependency installation in your sandbox Dockerfile:

dockerfile
ENV UV_NO_BUILD=true

Use ENV UV_NO_BUILD=false in a derived image to remove this restriction. False or unset values leave the decision to the notebook's uv configuration.

For the example E2B template, add this line to files/marimo.sh and rebuild the template:

sh
export UV_NO_BUILD="${UV_NO_BUILD:-true}"

This defaults to true and preserves an explicit false in the shell environment. The template's .setEnvs() affects build steps only.

Set the variable in the sandbox environment before dependency setup starts. The hub server's environment does not configure remote sandboxes. Image changes apply to new sandboxes, subject to the snapshot behavior described above.

Build exceptions depend on the uv version and command. See UV_NO_BUILD. This setting does not isolate notebook code or protect session credentials.

Why pre-install (not just cache) ​

uv sync builds a notebook's environment at startup. Even with a warm download cache, materializing marimo + its recommended extras (~70 packages) into a fresh venv takes a few seconds — paid on every kernel. Instead, pre-install marimo and your common libraries into the image's project environment. Then uv sync --inexact only needs to add a notebook's extra deps (and keeps the base), so the common case is a near-instant no-op.

Pin marimo to a specific version so it's consistent and never silently upgrades; bump it deliberately. Encode the Python + marimo version in the image tag (e.g. py3.13-marimo0.23.10) so each image is traceable.

Build your own ​

The examples/sandbox-image/ directory is a ready-to-fork starting point. marimo is pinned via the MARIMO_VERSION build arg and installed alongside the libraries in warm/pyproject.toml into /opt/marimohub/venv:

dockerfile
FROM python:3.13-slim
ARG MARIMO_VERSION=0.23.10
ENV MARIMO_VERSION=${MARIMO_VERSION}
COPY --from=ghcr.io/astral-sh/uv:0.10.9 /uv /uvx /usr/local/bin/
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
 && rm -rf /var/lib/apt/lists/*
ENV UV_PROJECT_ENVIRONMENT=/opt/marimohub/venv UV_LINK_MODE=copy
# Skip marimo's PyPI version ping, and continuously snapshot the notebook to
# __marimo__/notebook.html so the host captures it on teardown.
ENV MARIMO_SKIP_UPDATE_CHECK=1 _MARIMO_APP_OVERLOAD_AUTO_DOWNLOAD=[html]
RUN useradd -m appuser && mkdir -p /workspace && chown -R appuser:appuser /workspace
COPY warm/pyproject.toml /tmp/base/pyproject.toml
RUN cd /tmp/base && uv add --compile-bytecode "marimo[recommended]==${MARIMO_VERSION}" \
 && uv cache clean && rm -rf /tmp/base && chown -R appuser:appuser /opt/marimohub
ENV VIRTUAL_ENV=/opt/marimohub/venv PATH=/opt/marimohub/venv/bin:$PATH
USER appuser
WORKDIR /workspace

Build with a version-stamped tag, push, and point MARIMOHUB_COMPUTE_IMAGE at it:

sh
docker build --build-arg MARIMO_VERSION=0.23.10 \
  -t ghcr.io/orgname/marimo-sandbox:py3.13-marimo0.23.10 examples/sandbox-image
docker push ghcr.io/orgname/marimo-sandbox:py3.13-marimo0.23.10

To upgrade marimo, bump MARIMO_VERSION and rebuild. To change the pre-installed libraries, edit warm/pyproject.toml; add system libraries with extra apt-get install lines.

Private registries ​

BackendHow to authenticate the pull
KubernetesCreate an image pull secret; set MARIMOHUB_COMPUTE_KUBERNETES_IMAGE_PULL_SECRET to its name.
Dockerdocker login on the server's Docker daemon — its credentials pull the image.
Podmanpodman login for the server user or configured remote connection.
ModalConfigure registry credentials in Modal, or use a named image (modal://name[:tag]).
CoreWeaveConfigure registry credentials in CoreWeave; pass the image reference.

Private package indexes ​

Configure private Python package indexes with the package registry integrations, not with image ENV lines. The hub injects the uv index settings and credentials into each editor session and job before uv sync starts. Restricted viewer sandboxes receive no integrations, so private dependencies do not resolve there. Presets exist for AWS CodeArtifact, JFrog Artifactory, Azure Artifacts, and GitLab. For any other index (Nexus, devpi, Google Artifact Registry, an internal mirror), use the generic Python package index integration with the index's simple-API URL.

How injected values combine with other sources:

  • If an Environment variables integration also sets UV_INDEX, the hub merges the values: package registry entries first, then the environment-variable entries, without duplicates.
  • An Environment variables integration that sets UV_DEFAULT_INDEX to a value different from the package registry's stops session creation. Use the registry's Default index option instead.
  • An ENV UV_INDEX or ENV UV_DEFAULT_INDEX in the sandbox image applies only while no integration sets that variable. An injected value replaces it.

uv sync runs once at session start. An index change applies to new sessions, not to running kernels.

Notebook URL bridge ​

Hub installs the bundled notebook bridge into the active marimo interpreter before the CLI starts. Both uv-sync-edit and uv-script-pins support installation. The installer uses uv pip install --no-deps --no-index and preserves the installed marimo version. Custom environment paths work through sys.executable.

The wheel and launcher live outside captured notebook files. An artifact identity prevents repeat installation. New environments and changed artifacts trigger installation again. Installation failures preserve the normal launch behavior.

No image change is required. The environment must permit local wheel installation. See the bridge package guide for timeout, fallback, and compatibility details.

Build the maintained image ​

The maintained images/marimo-sandbox image includes the notebook bridge wheel. Its default, base, release, vscode, opencode, and tools targets all require the notebook_bridge named build context. Builds without this context fail.

From the repository root, use:

sh
docker build --build-context notebook_bridge=packages/notebook-bridge \
  -t marimo-sandbox:local images/marimo-sandbox

For an optional target, add --target vscode, --target opencode, or --target tools. The publish workflow supplies this context automatically. At launch, the bridge checks its artifact identity and skips installation when the identity matches. Custom images and older images retain the offline installation fallback.

For a standalone image without bridge preinstallation, use examples/sandbox-image. That Dockerfile needs no named context. Hub installs the bridge at launch.

Provider-agnostic. Deploy anywhere.