Summary
The Docker server image currently creates and mounts /root/.mux, but exports MUX_HOME=/root/.mux. The application resolver (getMuxHome() in src/common/constants/paths.ts) recognizes MUX_ROOT, not MUX_HOME.
The root image works today only because os.homedir() resolves to /root. Under docker run --user ..., that implicit fallback can select a different home directory, bypass the mounted volume, or fail on writes.
Proposed contract
- Make
MUX_ROOT the explicit Docker image contract: set MUX_ROOT=/root/.mux in Dockerfile.
- Retain the root default and the existing
docker-compose.yml persistence target, mux-data:/root/.mux, so existing volumes and bind mounts remain compatible.
- Do not add
MUX_HOME as an application alias: MUX_ROOT is the existing resolver override and dual-variable precedence would create an unnecessary compatibility surface.
- Treat non-root execution as an explicit operator configuration: users running the image with
--user must provide a writable, mounted MUX_ROOT path with compatible ownership. The image should not infer a different home, chown mounted volumes, or migrate state.
Implementation and validation
- Replace the Dockerfile
MUX_HOME environment declaration with MUX_ROOT=/root/.mux, retaining directory creation and the current volume location.
- Add a behavioral unit test in
src/common/constants/paths.test.ts for getMuxHome() proving MUX_ROOT is returned exactly and restoring mutated environment variables afterward. If practical, also prove MUX_HOME alone does not alter resolution.
- Run the focused test, Dockerfile lint/static checks, and a local image build/smoke test when Docker is available.
Existing CI already has Docker image startup smoke coverage in .github/workflows/pr.yml, but it checks health/version rather than persistence-root resolution. The resolver test should protect the actual contract without adding a brittle filesystem-only container assertion.
Context
A prior search found no exact duplicate. #3699 may be a user-facing symptom but is unconfirmed. PR #849 introduced the current image convention; PR #1587 is related precedent for non-root runtime handling.
Generated with mux • Model: openai:gpt-5.6-terra • Thinking: high • Cost: $0.04
Summary
The Docker server image currently creates and mounts
/root/.mux, but exportsMUX_HOME=/root/.mux. The application resolver (getMuxHome()insrc/common/constants/paths.ts) recognizesMUX_ROOT, notMUX_HOME.The root image works today only because
os.homedir()resolves to/root. Underdocker run --user ..., that implicit fallback can select a different home directory, bypass the mounted volume, or fail on writes.Proposed contract
MUX_ROOTthe explicit Docker image contract: setMUX_ROOT=/root/.muxinDockerfile.docker-compose.ymlpersistence target,mux-data:/root/.mux, so existing volumes and bind mounts remain compatible.MUX_HOMEas an application alias:MUX_ROOTis the existing resolver override and dual-variable precedence would create an unnecessary compatibility surface.--usermust provide a writable, mountedMUX_ROOTpath with compatible ownership. The image should not infer a different home, chown mounted volumes, or migrate state.Implementation and validation
MUX_HOMEenvironment declaration withMUX_ROOT=/root/.mux, retaining directory creation and the current volume location.src/common/constants/paths.test.tsforgetMuxHome()provingMUX_ROOTis returned exactly and restoring mutated environment variables afterward. If practical, also proveMUX_HOMEalone does not alter resolution.Existing CI already has Docker image startup smoke coverage in
.github/workflows/pr.yml, but it checks health/version rather than persistence-root resolution. The resolver test should protect the actual contract without adding a brittle filesystem-only container assertion.Context
A prior search found no exact duplicate. #3699 may be a user-facing symptom but is unconfirmed. PR #849 introduced the current image convention; PR #1587 is related precedent for non-root runtime handling.
Generated with
mux• Model:openai:gpt-5.6-terra• Thinking:high• Cost:$0.04