Bring your own backends
Choose your storage, compute, and identity providers β and switch them later without migrating your notebooks.
A provider-agnostic platform to store, manage, and run marimo notebooks. No database β bring your own storage, compute, and identity.
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.
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.
# --- 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=sourceHow to provision and connect each backend you picked above.
Works with any S3-compatible store (AWS S3, MinIO, Tigris, Ceph, CoreWeave CAIOS, or Cloudflare R2 via its S3 endpoint).
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.
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)
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.
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.
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 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.
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.
https://hub.example.com/api/auth/callback.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.
https://hub.example.com/api/auth/callback.# 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=β¦
https://hub.example.com/api/auth/callback as a Sign-in redirect URI.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.
https://hub.example.com/api/auth/callback.# 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 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.