Feature/docker compose - #349
Conversation
Ark0N
commented
Aug 30, 2026
Thanks for this, and thanks especially for the write-up. The motivation section, the storage/migration notes and the explicit Docker-outside-of-Docker framing made this much faster to review than most infrastructure PRs. How I verified: merged the PR head into current What is clearly rightThe Claude-resume fix is a genuine bug and it is not Compose-specific.
The root The swap-limit compatibility path preserves the real exit status and re-inspects on a lost create race, and the comment explaining why command substitution would expand too early through the nested bash/tmux layers is correct. Blocking1.
2. Nothing refuses to start with the placeholder password. Correctness3. 4. A missing So a first run against a not-yet-existing cases path leaves the unprivileged runtime account unable to write cases into it. The start script already does exactly the right checks for 5. The concurrent-create tolerance only exists in swap-compatibility mode. The 6.
Since the appdata bind is an exact HOME-to-host mapping, running the workspace Image and packaging7. That is the full engine plus containerd and runc, and on bookworm it is Docker 20.10.24 (2023). 8. CLI coverage is behind 9. 10. The self-update claim does not hold. Documentation and polish
SummarySolid work with real end-to-end validation behind it, and it fixes a genuine bug on the way past. Suggested path:
Items 7 to 10 and the documentation list are not blockers, though 7 and 8 are cheap and would make the image match what the description promises. |
…iner feat(docker): attach a case to an already-running container Conflicts came from work that landed after the PR was opened, and each is resolved onto the newer abstraction rather than by keeping the older code: - `defaultDockerCommandForMode` is registry-driven since #347, so the PR's `runsAsRoot` arm became `overlays.docker.rootCommand` (claude only). Claude Code still refuses `--dangerously-skip-permissions` as root in 2.1.261 and the refusal is visible only inside the container, so an adopted root container otherwise just shows a dead pane. Which flag to drop is a per-CLI fact, and `test/cli-registry-no-id-branching.test.ts` forbids expressing it as a branch. - The probe's mode list and its mode -> binary table both duplicated the registry. They now read `enabledCliIds()` / `discovery.binaries[0]`, which is also what fixes the merge's silent regression: the hand-written list predates `omp`, and the run menu gates every docker case on this probe, so owned containers would have lost that mode. `shell` needs no arm — it declares no binary, so it is dropped from the lookup and reported available regardless. - The per-mode `mode === 'claude' && !cliDir` chain in `tmux-manager.ts` is one `missingCliMessage(mode)` gate since #347; the PR's docker exemption moved onto it. Its test now pins the single gate instead of counting seven arms. - The create arm keeps #349's swap-limit warning filter, which the adopted arm never reaches; the run-mode list gains `omp` from #353. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TecFD9hvPYJ1mkkMtBQbT1
Summary
This PR adds first-party Docker Compose support for Codeman while preserving its ability to create and manage isolated agent containers.
With this PR Codeman itself runs inside a container & it can still spawn isolated Claude, Codex, Gemini, OpenCode, and shell case containers through the host Docker daemon. These are sibling containers, not nested Docker-in-Docker containers.
The Compose deployment:
Motivation
Codeman was originally designed to run directly on a host where Codeman and the Docker daemon shared the same filesystem namespace.
Not everyone want's to run applications directly on their desktop and docker is a perfect way to isolate systems. It also allows end-users to run linux applications on Windows using WSL2.
Docker containers also run as a system level so you don't even have to log into your desktop in order to use CM because docker is running as a service.
Future development of CM can also be adapted to spin up named containers based on the branch name to test multiple feature and bugfix additons with AI. Simply ask your AI to spin up a new instance on a new branch with a prefix.
Docker build vs published Image
This setup is building the docker image manually, there's no requirement for actions/pipelines to create an image and publish it to dockerhub (although this can be added at any point in time).
Creating the CM container this way at this point of CM's development allows for a simple git pull and then re-rerun the Start-Container.sh script to rebuild the image with the latest version or use CM's built-ni self update tool.
CodeMan can also self-update within the container and just requires a restart to use the latest version. A future idea would be to add in a self-terminate and restart capability to the container doesn't even need to be restarted.
Application Data
Codemans' user folder is passed mapped through to a docker host volume or folder, preserving all history during rebuilds or updates.
User running context
Running Codeman as root was not a suitable workaround because some agent CLIs, particularly Claude Code, refuse or fail to operate correctly as root. A dedicated user is created during the container build, username is set as per the .env file.
Isolated container support
The Compose deployment uses Docker-outside-of-Docker:
/var/run/docker.sock.CODEMAN_CASES_PATHprovides a workspace path that is identical inside Codeman and on the Docker host.CODEMAN_DOCKER_HOST_HOMEtranslates generated credential, seed, transcript, and hook-secret paths from Codeman's container filesystem into host-visible paths.Docker itself is not run as a daemon inside the Codeman container. Only the Docker CLI is installed in the image.
Direct host installations continue using the existing behaviour because path translation is enabled only when
CODEMAN_DOCKER_HOST_HOMEis configured.Changes
docker/server.Dockerfiledocker/.env.exampledocker/Start-Codeman.shopencode.CODEMAN_RUNTIME_USER.CODEMAN_CASES_PATHfor host-visible isolated workspaces.CODEMAN_DOCKER_HOST_HOMEtranslation for:CODEMAN_DOCKER_DISABLE_SWAP_LIMITcompatibility for hosts without swap accounting:--memory-swapoption.Security
no-new-privilegesis enabled..env.examplemust be replaced before exposing Codeman to a network.Backwards compatibility
Direct host installations retain their existing behaviour when the new Compose-specific environment variables are absent.
Existing isolated Docker cases continue using the normal host-native path handling outside Compose deployments.
Swap limiting retains its current behaviour unless
CODEMAN_DOCKER_DISABLE_SWAP_LIMIT=1is explicitly configured.Validation
The complete validation suite passed in a clean Linux container with Node.js 22 and tmux installed:
The following checks passed:
npm run typecheck npm run lint npm run format:check npm run check:frontend-syntax npm test bash -n docker/Start-Codeman.sh docker compose --env-file docker/.env.example \ -f docker/docker-compose.yaml config --quietThe server image was built and validated with multiple host identity combinations:
Manual end-to-end validation was performed on an Unraid Docker host, including:
opencodeaccount.