Status: verified locally (2026-09-10, evidence below): the image builds,
runs the Playwright smoke, keeps its state on a volume across restarts, and
stops on SIGTERM. Publishing to GHCR runs on every push to main and every
v* tag (.github/workflows/image.yml); the run for the publishing commit
is recorded below.
The image is the Node build of the app (Dockerfile, multi-stage on
node:22-alpine, runs as the node user, listens on PORT, default 3000,
health check on GET /api/health). It sets OPENMUSE_STATE_DIR=/data for the
session map (mount a volume there). Everything else comes from the
environment (.env.example).
npm ci
npx opencomputer login
npm run setup -- --target docker # secrets and the project into .env.local; prints the steps
docker run -d --name openmuse -p 3000:3000 \
--env-file .env.local \
-v openmuse-data:/data \
ghcr.io/diggerhq/openmuse:latestThe package is private while the repository is: docker login ghcr.io with a
GitHub token that has read:packages, or build it yourself with
docker build -t openmuse ..
Put an https origin in front of port 3000 (your reverse proxy, or a tunnel
such as ngrok http --domain=<host> 3000); the agents call back to that
origin and the browser's cookie is Secure. The server trusts
X-Forwarded-Proto and X-Forwarded-Host from that proxy (src/server.ts),
so do not expose the container to the internet without one. Then:
npm run setup -- --origin https://<that origin> # deploys the agents pinned to itOpen the origin and sign in with OPENMUSE_OWNER_SECRET from .env.local;
the sign-in registers the installation secret with the platform for that
origin.
The state directory holds state.json, the session map: which coordinator
session is live and which worker session each topic has. Notes are project
memory on OpenComputer, not on the volume. On a host without a volume set
OPENMUSE_STATE_STORE=memory: after a restart the coordinator session is
found again by its key and each topic gets a fresh worker on its next task.
Local build with Docker 29.5 (colima, arm64) on 2026-09-10:
docker build -t openmuse:local .: 43.6 s cold (dependency install dominates; the build step itself is 2 s). Imageopenmuse:local475 MB (Node 22 on Alpine 3.24 is 170 MB of it; productionnode_modules205 MB, of whichlucide-react44 MB and@tanstack/start-plugin-corewith itsesbuild/prettier/@babeldependencies 60 MB; the app's owndist/is 3 MB).docker run -d -p 3300:3000 --env-file .env.local -v openmuse-matrix-test:/data openmuse:local:GET /api/healthreturned{"ok":true,"stateStore":"fs","environment":"development"}in 111 ms;/redirected to/login; the log showsreturn_path.timer_started(interval 2 s); Docker reported the containerhealthyafter the start period.- Playwright smoke (
BASE_URL=http://localhost:3300 npx playwright test e2e/smoke.spec.ts --project=desktop): 2 passed in 1.3 s, against the real coordinator session in OpenComputer Development. - The volume held
state.jsonandmemory/profile/owner.jsonplus the topics after the run;docker stoptook 1 s (SIGTERM honoured by the in-process server, no kill wait); afterdocker startthe health check answered 200 andstate.jsonwas unchanged. Container and volume removed. - GHCR: the push of the publishing commit (
217e9bb) ran.github/workflows/image.yml(run 34537570306): success in 9 min 25 s (the arm64 half runs under QEMU), pushedghcr.io/diggerhq/openmuse:mainandghcr.io/diggerhq/openmuse:sha-217e9bb, multi-arch manifestsha256:c92435a0…. Pulling from this machine with a token withoutread:packageswas refused (denied), as the private package should.