- Dockerfile 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitignore | ||
| compose.yml | ||
| Dockerfile | ||
| example.env | ||
| README.md | ||
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
.pyfiles. 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 startsmarimo 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):
- Command palette (
Ctrl/Cmd+K) → Slides, or the app-preview layout dropdown. - Keep the notebook’s
layouts/folder. The hub versions those files with the project. - Present live, or Run as app so others get the deck without the editor.
- 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.
- In Authentik, create an OAuth2/OpenID Provider and Application
(guide).
Client type Confidential. Scopes
openid email profile(add groups if you will map roles). - Redirect URI must match exactly:
http://localhost:3000/api/auth/callback(usehttps://hub.example.com/api/auth/callbackonce you terminate TLS). - Copy the issuer from Authentik’s OpenID configuration. It is usually
https://authentik.example.com/application/o/<app-slug>/(trailing slash matters). - In
.env:- Comment the
MARIMOHUB_AUTH_BACKEND=devblock (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_ADMINSto your real email. - Optional:
MARIMOHUB_DEFAULT_ROLE=noneso users only see their projects; group claims as commented.
- Comment the
- 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.