Getting started
marimohub runs on your infrastructure. An operator usually does four things: try it locally, choose production backends, generate configuration, then deploy.
1. Try the local stack
Run the hub with no external services before choosing providers:
pnpm install --frozen-lockfile
pnpm devThe local stack uses memory storage, local subprocess compute, and dev auth. It is useful for evaluation and development, but it is not durable and must not serve real users. See Testing locally for details.
2. Choose production backends
Every deployment picks one option for each port.
| Decision | Common options | Guide |
|---|---|---|
| Storage - where notebooks live | CAIOS, AWS S3, GCS, Azure Blob Storage, MinIO, R2 | Storage |
| Compute - where kernels run | CoreWeave Sandboxes, Modal, Kubernetes, Docker, Podman | Compute |
| Auth - who can sign in | OpenID Connect (Google, Okta, Auth0), Cloudflare Access | Auth |
Storage is the decision to make first. It holds the durable state and must support atomic conditional writes.
Optional capabilities:
- Managed AI gives notebook users an AI assistant without exposing the upstream provider key.
- Environment & cloud access configures data sources, environment variables, secret sources, and federated cloud access.
- Syncing serves read-only notebooks whose source of truth is pushed in from another system.
3. Generate configuration
Most operators use environment variables and the prebuilt server. Teams with custom adapters can compose the packages directly as a library. See Deployment options for the trade-off.
Use the configurator to generate a .env, Helm values, Docker Compose service, or equivalent library wiring. The result is a scaffold: replace every _replace_me_ value, using its # e.g. comment as a guide, then review the optional values before deploying.
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=sourceSetup
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).
- Create a bucket for marimohub.
- Get credentials with read/write on that bucket (an access key + secret), or rely on the instance/SDK default credential chain on AWS.
- 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 →
- 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_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.
- In the Google Cloud Console, open APIs & Services → Credentials.
- Create Credentials → OAuth client ID, application type Web application.
- Under Authorized redirect URIs, add
https://hub.example.com/api/auth/callback. - 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
- In the Entra admin center (or Azure Portal), go to App registrations → New registration.
- Set a Web redirect URI of
https://hub.example.com/api/auth/callback. - 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=…
Okta
- In the Okta Admin Console, open Applications → Create App Integration.
- Choose OIDC - OpenID Connect and Web Application.
- Add
https://hub.example.com/api/auth/callbackas a Sign-in redirect URI. - 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
- In the Auth0 Dashboard, open Applications → Create Application and pick Regular Web Application.
- Under Settings → Allowed Callback URLs, add
https://hub.example.com/api/auth/callback. - 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.
Non-interactive configuration scaffold
Agents and terminal-only workflows can start from this equivalent S3 + Modal + OIDC scaffold:
MARIMOHUB_STORAGE_BACKEND=s3
MARIMOHUB_STORAGE_S3_BUCKET=_replace_me_ # e.g. orgname-marimohub
MARIMOHUB_COMPUTE_BACKEND=modal
MARIMOHUB_COMPUTE_IMAGE=_replace_me_ # e.g. ghcr.io/orgname/marimo-sandbox:latest
MARIMOHUB_COMPUTE_MODAL_TOKEN_ID=_replace_me_
MARIMOHUB_COMPUTE_MODAL_TOKEN_SECRET=_replace_me_
MARIMOHUB_AUTH_BACKEND=oidc
MARIMOHUB_AUTH_OIDC_ISSUER=_replace_me_ # e.g. https://accounts.example.com
MARIMOHUB_AUTH_OIDC_CLIENT_ID=_replace_me_
MARIMOHUB_AUTH_OIDC_CLIENT_SECRET=_replace_me_
MARIMOHUB_AUTH_OIDC_REDIRECT_URI=_replace_me_ # e.g. https://hub.example.com/api/auth/callback
MARIMOHUB_AUTH_SESSION_SECRET=_replace_me_
MARIMOHUB_AUTH_ALLOWED_EMAIL_DOMAINS=_replace_me_ # e.g. example.com,example.orgChange the selector values and follow Storage, Compute, and Auth for other backends. The generated Configuration reference is authoritative for variable names, defaults, and requirements.
The full generated reference is Configuration.
4. Deploy and validate
Choose the guide closest to your platform:
After deploy, check /api/health, sign in through your auth backend, create a test project, start a kernel, and confirm a saved notebook survives a restart.
For production operation, read Security, Operations, and Troubleshooting.