Skip to content

marimohubSelf-hostable marimo notebooks

A provider-agnostic platform to store, manage, and run marimo notebooks. No database β€” bring your own storage, compute, and identity.

What marimohub is ​

marimohub is a self-hostable platform for storing, managing, and running marimo notebooks. Bring your own object storage, sandbox provider, and identity system; the hub provides the web app, API, version history, access control, and kernel lifecycle.

Configure your deployment ​

Pick your storage, compute, and auth backends to generate a clearly marked config scaffold (.env, Helm, or Docker Compose) and the equivalent library wiring code β€” live, no install required.

For a terminal-only equivalent, use the non-interactive configuration scaffold.

Any S3-compatible store: CoreWeave CAIOS, AWS S3, MinIO, Tigris, Ceph, or Cloudflare R2 via its S3 endpoint. Point `*_S3_ENDPOINT` at the provider.

Modal sandboxes.

App-native OpenID Connect (the production backend). If the allowlist contains one domain, Google receives it as the `hd` hint.

No managed AI. The marimo assistant still works if a user supplies their own key in marimo settings.

Options

Controls whether editors share one persistent sandbox per notebook (`shared`) or one editor owns it (`exclusive`). In `exclusive` mode, other editors can start temporary sandboxes or confirm a takeover. This setting does not affect apps or viewer sessions. See [Editor sessions](./editor-sessions.md).

Which sandbox working-dir files survive a session (source | workspace). `source` persists only the source files (notebook.py + pyproject.toml); `workspace` also captures runtime files (e.g. generated data) into the notebook workspace on teardown and restores them on the next session.

Container image with marimo + uv + python, or a comma-separated list of such images: the first is the default and the rest are selectable per notebook as base images. Required by the `modal` backend; recommended for `coreweave`.

# --- Storage ---
MARIMOHUB_STORAGE_BACKEND=s3
MARIMOHUB_STORAGE_S3_BUCKET=_replace_me_  # e.g. orgname-marimohub
MARIMOHUB_STORAGE_S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
MARIMOHUB_STORAGE_S3_REGION=us-east-1
MARIMOHUB_STORAGE_S3_ACCESS_KEY_ID=  # secret
MARIMOHUB_STORAGE_S3_SECRET_ACCESS_KEY=  # secret
MARIMOHUB_STORAGE_S3_FORCE_PATH_STYLE=true

# --- Compute ---
MARIMOHUB_COMPUTE_BACKEND=modal
MARIMOHUB_COMPUTE_IMAGE=_replace_me_  # e.g. ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_SANDBOX_HOSTNAME=hub.example.com
MARIMOHUB_COMPUTE_WORKDIR=/workspace
MARIMOHUB_COMPUTE_ASSET_URL=https://cdn.jsdelivr.net/npm/@marimo-team/frontend@{version}/dist
MARIMOHUB_SANDBOX_STARTUP_TIMEOUT_SECONDS=120
MARIMOHUB_COMPUTE_MODAL_TOKEN_ID=_replace_me_  # required, secret
MARIMOHUB_COMPUTE_MODAL_TOKEN_SECRET=_replace_me_  # required, secret
MARIMOHUB_COMPUTE_MODAL_ENVIRONMENT=notebooks
MARIMOHUB_COMPUTE_MODAL_APP_NAME=marimohub

# --- Auth ---
MARIMOHUB_AUTH_BACKEND=oidc
MARIMOHUB_AUTH_OIDC_ISSUER=_replace_me_  # e.g. https://accounts.example.com
MARIMOHUB_AUTH_OIDC_CLIENT_ID=_replace_me_  # required
MARIMOHUB_AUTH_OIDC_CLIENT_SECRET=_replace_me_  # required, secret
MARIMOHUB_AUTH_OIDC_REDIRECT_URI=_replace_me_  # e.g. https://hub.example.com/api/auth/callback
MARIMOHUB_AUTH_OIDC_AUDIENCE=
MARIMOHUB_AUTH_OIDC_PROMPT=consent
MARIMOHUB_AUTH_SESSION_SECRET=_replace_me_  # required, secret
MARIMOHUB_AUTH_ALLOWED_EMAIL_DOMAINS=_replace_me_  # e.g. example.com,example.org

# --- Managed AI ---
MARIMOHUB_AI_BACKEND=none

# --- Options ---
MARIMOHUB_EDITOR_SANDBOX_SHARING=shared
MARIMOHUB_PERSIST_WORKSPACE=source

Setup

How to provision and connect each backend you picked above.

StorageS3 / S3-compatible Full docs β†’

Works with any S3-compatible store (AWS S3, MinIO, Tigris, Ceph, CoreWeave CAIOS, or Cloudflare R2 via its S3 endpoint).

  1. Create a bucket for marimohub.
  2. Get credentials with read/write on that bucket (an access key + secret), or rely on the instance/SDK default credential chain on AWS.
  3. Set the env:
MARIMOHUB_STORAGE_BACKEND=s3
MARIMOHUB_STORAGE_S3_BUCKET=orgname-marimohub
MARIMOHUB_STORAGE_S3_ENDPOINT=https://s3.us-east-1.amazonaws.com  # omit for AWS
MARIMOHUB_STORAGE_S3_REGION=us-east-1
MARIMOHUB_STORAGE_S3_ACCESS_KEY_ID=…       # secret β€” or use the SDK default chain
MARIMOHUB_STORAGE_S3_SECRET_ACCESS_KEY=…   # secret

Your store must support conditional writes

marimohub uses S3 If-Match to update notebooks safely and refuses to start on a store that doesn't honor it. AWS S3, R2, Tigris, CoreWeave CAIOS, and recent MinIO all qualify; very old MinIO/Ceph builds may not.

MinIO / Ceph need path-style addressing

Set MARIMOHUB_STORAGE_S3_FORCE_PATH_STYLE=true for MinIO and Ceph, or requests to the bucket will fail to resolve.

CoreWeave CAIOS

CAIOS (CoreWeave AI Object Storage) is CoreWeave's S3-compatible store and the storage half of the CKS deployment. Use the s3 backend and point the endpoint at CAIOS:

MARIMOHUB_STORAGE_BACKEND=s3
MARIMOHUB_STORAGE_S3_BUCKET=orgname-marimohub
MARIMOHUB_STORAGE_S3_ENDPOINT=https://cwobject.com
MARIMOHUB_STORAGE_S3_ACCESS_KEY_ID=…         # CAIOS access key (secret)
MARIMOHUB_STORAGE_S3_SECRET_ACCESS_KEY=…     # CAIOS secret key (secret)
ComputeModal Full docs β†’
  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:
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.

AuthOIDC Full docs β†’

App-native OpenID Connect is the production backend. marimohub discovers the provider endpoints from /.well-known/openid-configuration. You supply the issuer, client credentials, and redirect URI.

MARIMOHUB_AUTH_BACKEND=oidc
MARIMOHUB_AUTH_OIDC_ISSUER=https://accounts.example.com
MARIMOHUB_AUTH_OIDC_CLIENT_ID=…
MARIMOHUB_AUTH_OIDC_CLIENT_SECRET=…
MARIMOHUB_AUTH_OIDC_REDIRECT_URI=https://hub.example.com/api/auth/callback
MARIMOHUB_AUTH_SESSION_SECRET=…            # signs the session cookie (HS256, β‰₯32 bytes)
MARIMOHUB_AUTH_ALLOWED_EMAIL_DOMAINS=example.com  # REQUIRED allowlist (verified email); `*` allows all
# MARIMOHUB_AUTH_OIDC_AUDIENCE=…           # deprecated and ignored; aud must contain the client ID
# MARIMOHUB_AUTH_OIDC_PROMPT=consent       # optional: override the default (select_account) OAuth prompt
# MARIMOHUB_AUTH_OIDC_SCOPES="openid email profile groups" # add only provider-required scopes

The redirect URI is always https://<your-host>/api/auth/callback. Register this exact value with your provider. A different value causes a redirect_uri_mismatch error. ALLOWED_EMAIL_DOMAINS is required. Set one or more domains, or set * to allow all.

If the provider publishes UserInfo, marimohub uses it for profile claims. UserInfo must have the same sub as the validated ID token. Email verification is required by default. If a trusted issuer omits email_verified, use MARIMOHUB_AUTH_OIDC_EMAIL_VERIFICATION=trusted-issuer. This mode also permits an omitted claim when a domain allowlist is active. If the claim is present, its value must be boolean true.

The signed session JWT has a 3,800-byte limit. If necessary, marimohub omits the profile picture first and the display name second. Required identity and authorization claims are never omitted. Login fails if they exceed the limit.

The issuer, callback, discovered authorization, and discovered logout endpoints must use HTTPS and cannot contain embedded credentials.

Groups and roles

Group authorization is optional and uses exact provider group IDs. Set a JSON Pointer to the provider array. Then set at least one group policy:

MARIMOHUB_AUTH_OIDC_GROUPS_CLAIM=/groups
MARIMOHUB_AUTH_OIDC_ALLOWED_GROUPS=hub-users
MARIMOHUB_AUTH_OIDC_SUPER_ADMIN_GROUPS=hub-platform-admins
MARIMOHUB_AUTH_OIDC_PROJECT_CREATION_GROUPS=hub-project-creators
MARIMOHUB_AUTH_OIDC_DEFAULT_VIEWER_GROUPS=hub-viewers
MARIMOHUB_AUTH_OIDC_DEFAULT_EDITOR_GROUPS=hub-editors
MARIMOHUB_AUTH_OIDC_DEFAULT_MANAGER_GROUPS=hub-project-managers

Nested claims use JSON Pointer syntax, such as /realm_access/roles. ALLOWED_GROUPS controls login. The other lists map groups to internal entitlements. The session cookie stores mapped entitlements, not raw groups.

PROJECT_CREATION_GROUPS controls who can create projects:

  • If the variable is not set, all authenticated users can create projects.
  • If the value is empty, only super admins can create projects.
  • If the value contains group IDs, super admins and matching users can create projects.

If no super admin is configured, an empty value prevents every user from creating projects. Setting the variable implies MARIMOHUB_PROJECT_CREATION=restricted, which also works without group mapping; combining it with MARIMOHUB_PROJECT_CREATION=open is rejected at startup.

An empty value does not require GROUPS_CLAIM. A non-empty value requires the claim and creates a group-derived session entitlement.

Group sessions last at most one hour by default. This limit bounds the delay after an IdP removes a user from a group. Kernels inherit the session JWT expiry as a fixed authorization deadline. Active editors cannot extend it. Session reuse keeps the earliest caller credential deadline. At expiry, the lifecycle destroys the kernel and the proxy closes WebSockets. This teardown skips the final capture so that the kernel stops promptly. Periodic snapshots limit potential data loss.

Missing, malformed, or oversized group data cannot satisfy the login policy. marimohub accepts at most 200 group IDs. It does not resolve group-overage references from the provider. Configure the IdP to emit only the groups that marimohub needs. Group-derived roles and project-creation access apply only to the browser session. They do not transfer to personal access tokens.

After you enable project-creation groups, matching users must sign in again. Existing sessions do not contain the new entitlement.

For a strict rollout, first deploy the new version without the variable. Then set the variable after all replicas run the new version.

The user ID is the OIDC sub within the configured issuer. The same sub from another issuer can identify a different person. Therefore, an issuer URL change is an identity migration. Reconcile stored owners and members before the change.

Generate a session secret with openssl rand -base64 32.

Login-policy module

When a group mapping cannot express your access rule β€” for example, an approved department AND a minimum level AND a set of required attribute values β€” load a trusted login-policy module instead:

MARIMOHUB_AUTH_OIDC_LOGIN_POLICY_BACKEND=library
MARIMOHUB_AUTH_OIDC_LOGIN_POLICY_LIBRARY=/etc/marimohub/oidc-login-policy.mjs
# MARIMOHUB_AUTH_OIDC_LOGIN_POLICY_TIMEOUT_SECONDS=5        # 1–30; a timeout denies login
# MARIMOHUB_AUTH_OIDC_LOGIN_POLICY_SESSION_TTL_SECONDS=3600 # 300–3600

The built-in adapter still completes all OIDC protocol work: discovery, PKCE, state and nonce, ID-token verification, UserInfo subject binding, email verification, and the email-domain allowlist. The module runs after that validation and before session signing. It receives the validated ID-token and UserInfo claims as separate read-only objects and returns one bounded result: an allow or deny decision, plus the built-in entitlements (super-admin, project-creator, default-role:viewer, default-role:editor, default-role:manager). project-creator is only meaningful when MARIMOHUB_PROJECT_CREATION=restricted; without it every authenticated user can create projects. MARIMOHUB_AUTH_OIDC_LOGIN_POLICY_BACKEND=none (or unset) disables the module.

Login-policy configuration is mutually exclusive with the group variables above. A module can reproduce any group rule in code. The module applies to browser sessions only; personal access tokens never receive login-policy entitlements.

The module is trusted code and runs in-process with server privileges. Bundle it (with its dependencies) into one .mjs file, pin its version, and mount the same artifact on every replica. A module that fails to load stops the server at startup. During login, a policy denial shows the user a generic access-policy message; a policy error, timeout, or malformed result fails closed with the generic sign-in error and a bounded operator log event β€” the host never persists, logs, or writes raw claims into the session cookie. That guarantee covers the host only: the module sees every claim and runs with server privileges, so your policy code must not log or store claim values, and reviews should verify that it doesn't.

Policy sessions last at most one hour, like group sessions, which bounds the delay after an attribute or policy change. A module change requires a server restart and takes effect on the next login.

This feature maps identity to login eligibility and coarse roles. It is not resource-level access control: it cannot see projects or notebooks, and an entitlement never bypasses project-role checks. See Security for the boundary.

See examples/external-adapter/oidc-login-policy.mjs for a complete example.

Google

  1. In the Google Cloud Console, open APIs & Services β†’ Credentials.
  2. Create Credentials β†’ OAuth client ID, application type Web application.
  3. Under Authorized redirect URIs, add https://hub.example.com/api/auth/callback.
  4. Copy the Client ID and Client secret.
MARIMOHUB_AUTH_OIDC_ISSUER=https://accounts.google.com
MARIMOHUB_AUTH_OIDC_CLIENT_ID=…apps.googleusercontent.com
MARIMOHUB_AUTH_OIDC_CLIENT_SECRET=…
MARIMOHUB_AUTH_ALLOWED_EMAIL_DOMAINS=example.com   # a single domain is also sent to Google as the `hd` hint

The default OAuth prompt is select_account, which displays the Google account chooser. Set MARIMOHUB_AUTH_OIDC_PROMPT=consent to display the consent screen again.

See Google's OpenID Connect docs.

Microsoft Entra ID

  1. In the Entra admin center (or Azure Portal), go to App registrations β†’ New registration.
  2. Set a Web redirect URI of https://hub.example.com/api/auth/callback.
  3. From Overview, copy the Application (client) ID and Directory (tenant) ID; under Certificates & secrets, create a client secret.
# tenant-scoped issuer (use `organizations` or `common` for multi-tenant)
MARIMOHUB_AUTH_OIDC_ISSUER=https://login.microsoftonline.com/<tenant-id>/v2.0
MARIMOHUB_AUTH_OIDC_CLIENT_ID=<application-client-id>
MARIMOHUB_AUTH_OIDC_CLIENT_SECRET=…

See Microsoft's OIDC docs.

Okta

  1. In the Okta Admin Console, open Applications β†’ Create App Integration.
  2. Choose OIDC - OpenID Connect and Web Application.
  3. Add https://hub.example.com/api/auth/callback as a Sign-in redirect URI.
  4. Copy the Client ID and Client secret from the app's General tab.
MARIMOHUB_AUTH_OIDC_ISSUER=https://<your-org>.okta.com
MARIMOHUB_AUTH_OIDC_CLIENT_ID=…
MARIMOHUB_AUTH_OIDC_CLIENT_SECRET=…

If you use an Okta authorization server, the issuer is https://<your-org>.okta.com/oauth2/<server-id>. See Okta's OIDC docs.

Auth0

  1. In the Auth0 Dashboard, open Applications β†’ Create Application and pick Regular Web Application.
  2. Under Settings β†’ Allowed Callback URLs, add https://hub.example.com/api/auth/callback.
  3. Copy the Domain, Client ID, and Client Secret from Settings.
# note the trailing slash on the issuer
MARIMOHUB_AUTH_OIDC_ISSUER=https://<tenant>.auth0.com/
MARIMOHUB_AUTH_OIDC_CLIENT_ID=…
MARIMOHUB_AUTH_OIDC_CLIENT_SECRET=…

See Auth0's OIDC docs.

Managed AIOff Full docs β†’

Managed AI is off β€” the default:

MARIMOHUB_AI_BACKEND=none

The marimo AI assistant still works if a user supplies their own API key in marimo's settings. Turn on managed AI to provide a key for everyone.

Provider-agnostic. Deploy anywhere.