README.md

Self-hosted Executor

The single-container, self-hostable Executor server: the typed API, the MCP server, Better Auth (cookie / bearer / API-key + MCP OAuth), QuickJS code execution, and the web UI — all in one process over a libSQL (SQLite) file. No external database, worker, or proxy.

Run it

Using the published image:

docker run -d \
  --name executor-selfhost \
  -p 4788:4788 \
  -v executor-data:/data \
  ghcr.io/usefulsoftwareco/executor-selfhost:latest

Or build from a repository clone:

# From this directory:
docker compose up -d --build
# then open http://localhost:4788 and create the admin account

No configuration is required. A fresh instance shows a setup screen; the first person to create an account becomes the owner. After that, people join via single-use invite links you mint from the Admin page, and self-service signup is closed.

See .env.example for optional settings (most importantly EXECUTOR_WEB_BASE_URL behind a domain / TLS) and the full Self-Hosting guide for first-run, inviting people, backups, reverse-proxy setup, and upgrades.

Develop

bun run build                  # build the SPA (regenerates the route tree)
bun run src/serve.ts           # serve the built app
bun run --filter @executor-js/host-selfhost test   # the test suite

Layout

src/
  app.ts            the ExecutorApp.make composition root
  serve.ts          the Bun server entry
  config.ts         env + zero-config secret/key persistence
  auth/             Better Auth wiring, the signup gate, invite codes, seed
  account/          the AccountProvider seam (members/roles via the org plugin)
  admin/            the invite-code admin HttpApi
  system/           public /api/health + /api/setup-status
  db/ · mcp/ · execution.ts · plugins.ts · observability.ts
web/                the TanStack Router SPA (setup, login, join, admin, …)

Container permissions when upgrading

The container runs as UID/GID 65532:65532. New Docker named volumes inherit that ownership from the image. Existing volumes created by a root-running release must be backed up and assigned to this UID/GID before the first start of the new image. Bind-mounted data directories need the same write access.

Stop the old container before changing data-directory ownership. Verify the exact volume or bind-mount path, make a backup, and change ownership only within that data directory. The application does not change existing volume ownership automatically. Keep the previous image and backup available for rollback.

The Docker build and runtime base images are pinned by digest. Update the tag and digest together when applying upstream security updates.