No description
  • Dockerfile 100%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-21 11:12:42 +02:00
.gitignore first commit 2026-09-21 11:12:42 +02:00
compose.yml first commit 2026-09-21 11:12:42 +02:00
Dockerfile first commit 2026-09-21 11:12:42 +02:00
example.env first commit 2026-09-21 11:12:42 +02:00
README.md first commit 2026-09-21 11:12:42 +02:00

marimo self-host

A single-host Docker Compose stack for marimo notebooks, shaped like JupyterHub: a shared web hub, isolated kernels, and (later) real users via an existing identity provider.

Default login is a local dev bypass (one user, no password). Notebooks are plain files under ./data/marimohub. Presentations use marimo’s built-in Slides layout — no extra service.

This repository does not vendor marimo. It wraps published images and documents how to run them.

Stack

browser  -->  hub :3000 (marimohub)
                 |-- bind mount  ./data/marimohub
                 |-- docker.sock --> host Docker
                                    --> one sandbox container per kernel
Piece Choice
Control plane marimohub (ghcr.io/marimo-team/marimohub, tag from MARIMOHUB_IMAGE_TAG in .env, plus a thin local Dockerfile)
Storage Filesystem (fs), bind-mounted from ./data/marimohub. No database, no named Docker volumes.
Compute Docker: one container per session from ghcr.io/marimo-team/marimo-sandbox
Kernel access Proxied through the hub (http://localhost:3000). Kernel ports stay on loopback.
Auth dev by default. Switch to Authentik OIDC via .env only.
Network network_mode: host so the hub can reach kernel ports on 127.0.0.1

The published hub image does not include the Docker CLI. Dockerfile adds the static CLI and curl (Compose healthcheck against GET /api/health).

Limits: fs storage allows one hub replica. The Docker socket is root-equivalent on the host. Proxy kernel exposure is for trusted users (notebook code is same-origin with the hub).

Projects we use

  • marimo (GitHub, docs) — reactive Python notebooks stored as .py files. Git-friendly, runnable as scripts or apps.
  • marimohub (docs, single-instance deploy, configuration) — self-hostable hub: projects, members, versions, kernel lifecycle. Object storage (here: a directory) is the source of truth.
  • Sandbox image (ghcr.io/marimo-team/marimo-sandbox) — per-kernel runtime (Python, uv, marimo, polars, duckdb, pandas, …). The hub copies the notebook in and starts marimo edit.
  • Slides / app layouts — reveal.js presentations from notebook cells. Metadata lives in layouts/*.slides.json.
  • Authentik (OAuth2/OIDC) — optional later. Not part of this Compose file; point marimohub at a server you already run.
  • marimo-studio — experimental custom HTML/React/Svelte views. Not in this stack. Built-in slides are enough for web presentations.

Organization overview: github.com/marimo-team.

Compared to JupyterHub

JupyterHub This stack
Hub + spawners marimohub + MARIMOHUB_COMPUTE_BACKEND=docker
User home / notebook dir Projects in ./data/marimohub
Kernel images MARIMOHUB_COMPUTE_IMAGE (comma-separated; pick per notebook)
Resource specs MARIMOHUB_COMPUTE_PROFILES (small / medium by default)
Users / groups Identity provider + project roles (viewer / editor / manager). Default is a single local dev user.
Jupyter kernels marimo kernels only (not ipykernel)

Run

Do not start the stack until .env exists. These commands are for you to run locally; they are not executed as part of generating this repo.

cp example.env .env
docker pull ghcr.io/marimo-team/marimo-sandbox:latest
docker compose build
docker compose up -d
docker compose ps
curl -sf http://localhost:3000/api/health

./data/marimohub is created on first start; you do not need to mkdir it.

Open http://localhost:3000. Create a project and notebook, start a kernel, and confirm a sandbox container with docker ps. Restart the hub and confirm the notebook is still under data/marimohub.

Logs: docker compose logs -f hub. Stop: docker compose down (bind-mounted data is kept).

MARIMOHUB_IMAGE_TAG in .env selects the published hub image (default in example.env is 0.4.6). Comment it out or omit it to build from latest. Changing the tag requires docker compose build and then docker compose up -d.

Presentations

Inside a running notebook (full marimo editor):

  1. Command palette (Ctrl/Cmd+K) → Slides, or the app-preview layout dropdown.
  2. Keep the notebook’s layouts/ folder. The hub versions those files with the project.
  3. Present live, or Run as app so others get the deck without the editor.
  4. PDF (from the notebook environment): marimo export pdf notebook.py --as=slides.

Details: Notebooks as slides.

Authentication

Default: local dev login

example.env sets MARIMOHUB_AUTH_BACKEND=dev. Every request is the same local super-admin (user@localhost). Fine on a laptop; not for shared use. marimohub refuses to start if MARIMOHUB_AUTH_BACKEND is unset — see Auth.

Proxy kernel routing still needs MARIMOHUB_AUTH_SESSION_SECRET (≥32 bytes), even with dev auth. example.env includes a placeholder; replace it with openssl rand -base64 32 if this stack is more than a throwaway laptop.

Later: existing Authentik

Authentik is not added to Compose. Switching is env-only.

  1. In Authentik, create an OAuth2/OpenID Provider and Application (guide). Client type Confidential. Scopes openid email profile (add groups if you will map roles).
  2. Redirect URI must match exactly: http://localhost:3000/api/auth/callback (use https://hub.example.com/api/auth/callback once you terminate TLS).
  3. Copy the issuer from Authentik’s OpenID configuration. It is usually https://authentik.example.com/application/o/<app-slug>/ (trailing slash matters).
  4. In .env:
    • Comment the MARIMOHUB_AUTH_BACKEND=dev block (AUTH_DEV_* included).
    • Uncomment the Authentik block at the bottom of example.env.
    • Fill issuer, client id, client secret.
    • Set MARIMOHUB_AUTH_SESSION_SECRET (openssl rand -base64 32).
    • Set MARIMOHUB_AUTH_ALLOWED_EMAIL_DOMAINS (or * while testing).
    • Set MARIMOHUB_SUPER_ADMINS to your real email.
    • Optional: MARIMOHUB_DEFAULT_ROLE=none so users only see their projects; group claims as commented.
  5. Reload: docker compose up -d (no image rebuild). Open the hub — login should redirect to Authentik.

The local dev user is a different identity (sub) than Authentik. Notebooks created as user@localhost do not automatically belong to the Authentik user.

MARIMOHUB_AUTH_OIDC_EMAIL_VERIFICATION=trusted-issuer is commented in because Authentik sometimes omits email_verified. Drop it if your tokens already include email_verified: true.