Mayfly starts short-lived OpenChamber workspaces backed by OpenCode. Each browser session gets its own Docker sandbox, its own workspace, and its own OpenChamber UI password.
The sandboxes are intentionally disposable: no local editor setup, no persistent sandbox state, and automatic cleanup after disconnect.
flowchart LR
B([Browser]) -- HTTP + WS --> A[FastAPI app]
A -- reverse proxy --> M[[Mayfly sandbox<br/>OpenCode + OpenChamber]]
A -- Docker API --> M
- The FastAPI app creates and tracks sessions.
- Each session starts one sandbox container on the shared
mayfly-netDocker network — sandboxes have no host port mapping. - The browser visits
/view/{token}, gets a session cookie, and loads OpenChamber via a same-origin reverse proxy at/mayfly/. - The reverse proxy is a small in-house module (
app/services/proxy.py, HTTP viahttpx+ WS viawebsockets) — no third-party proxy dependency. - A lifecycle WebSocket (
/sessions/{token}/lifecycle) signals tab close so the sandbox is torn down after a short delay. $HOMEand/tmpinside the sandbox are tmpfs mounts, so every new session starts clean.docker/entrypoint.shgenerates OpenCode config, OpenChamber settings, the workspace directory, and a smallAGENTS.md.
- Docker with access to
/var/run/docker.sock - Python 3.13+ for local development
- An OpenAI-compatible model endpoint reachable from the sandbox, for example Ollama, vLLM, llama.cpp, or LM Studio
For model services running on the Docker host, use host.docker.internal in .env.
cp .env.example .env
docker compose --profile build build
docker compose up -dOpen http://localhost:8123.
When changing files under docker/, rebuild the sandbox image before starting new sessions:
docker compose --profile build build mayflyAll runtime configuration lives in .env.
| Variable | Purpose |
|---|---|
PUBLIC_URL |
external base URL (with scheme) used in API/MCP session links |
APP_PORT, APP_BIND_HOST |
FastAPI port and bind address |
TZ |
timezone applied to the app and every sandbox |
MAYFLY_IMAGE |
per-session sandbox image |
MAYFLY_MAX_SESSIONS |
max concurrent sessions |
MAYFLY_MEMORY, MAYFLY_CPUS |
per-sandbox resource limits |
MAYFLY_HOME_SIZE, MAYFLY_TMP_SIZE |
sandbox $HOME and /tmp tmpfs sizes |
MAYFLY_WORKSPACE_DIR |
workspace directory inside the sandbox home |
MAYFLY_UPLOAD_LIMIT |
max upload size into the workspace |
MAYFLY_CONNECT_TIMEOUT, MAYFLY_DISCONNECT_TIMEOUT |
cleanup timing for unused sessions |
OPENAI_BASE_URL, OPENAI_API_KEY, OPENAI_MODEL |
model endpoint passed to OpenCode |
OPENAI_CONTEXT_TOKENS, OPENAI_OUTPUT_TOKENS |
model limits passed to OpenCode |
OPENAI_TIMEOUT, OPENAI_CHUNK_TIMEOUT |
OpenCode provider request timeouts |
For offline or Nexus builds, override PIP_INDEX_URL, PIP_TRUSTED_HOST, NPM_REGISTRY, and NPM_STRICT_SSL.
GET /- browser entrypointPOST /view- create a browser session and redirect to/view/{token}GET /view/{token}- browser view for one session (setsmayfly_sessioncookie)POST /sessions- create a session via API, returnsurlandpasswordGET /sessions/status- active, available, limit, and total memory usage across all sessionsGET /sessions/{token}/status- same shape, but memory usage scoped to a single sessionPOST /sessions/{token}/upload- password-protected file upload into the workspaceDELETE /sessions/{token}- stop a sessionWS /sessions/{token}/lifecycle- browser lifecycle channel (drives the disconnect timeout)- any unmatched path - reverse-proxied to the sandbox identified by the
mayfly_sessioncookie (the browser iframe loads/mayfly/by convention) /docs- OpenAPI docs/mcp/- MCP endpoint
Static UI assets are served under /static/*; /favicon.ico serves the Mayfly logo.
The cookie-routed proxy means one active OpenChamber session per browser origin. Opening a second tab on the same origin overwrites the cookie and points API/WS traffic at the new session. The first tab detects this via a BroadcastChannel('mayfly-session') claim, hides its iframe, and closes its lifecycle WebSocket — that triggers the regular disconnect cleanup for the displaced container.
Sandbox containers run as an unprivileged user with a read-only root filesystem, dropped Linux capabilities, no-new-privileges, PID/memory/CPU limits, and tmpfs mounts for writable runtime state. Sandboxes have no host port mapping — they are only reachable through the app's reverse proxy.
Mayfly is still alpha. If you bind the app to a public interface, put it behind trusted network controls.
app/- FastAPI app, routers, services, templates, and static UI assetsdocker/Dockerfile.app- orchestrator imagedocker/Dockerfile.mayfly- per-session sandbox imagedocker/entrypoint.sh- sandbox startup config for OpenCode, OpenChamber, and workspace instructionsdocker-compose.yml- app service, build profile, and shared Docker network
