mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 06:29:42 +02:00
Three blockers, all fixed and verified by actually running the script (not just string-matching it): 1. `exec "$script_dir/Start-Codeman.sh"` failed EACCES/exit 126 on every checkout, since Start-Codeman.sh is committed non-executable (100644) — the same fact my own second commit on this branch established. Fixed to `exec bash "$script_dir/Start-Codeman.sh"`. 2. `down` ran before `build --no-cache`, so Codeman and every session it was running were offline for the entire rebuild, and a build failure left the stack down with nothing to bring it back — the exact ordering mistake Start-Codeman.sh's own "Build BEFORE taking the stack down" comment exists to prevent. Reordered to build, then down, then hand off. 3. The default path could throw the rebuild away: codeman-node-modules/ codeman-dist only re-seed from the image while EMPTY, Start-Codeman.sh only clears them when it detects the checkout's HEAD or package-lock.json moved, and neither condition is true for the Dockerfile-only change this script exists for — so a plain `bash docker/Update-Codeman.sh` rebuilt an image whose fresh node_modules/dist then sat unused behind the old volumes. Made clearing them the default; `--keep-volumes` opts out (replaces the old `--volumes`/`-v` flag, which is no longer needed since clearing is now the default). Smaller items from the same review, also fixed: - The --no-cache build now derives PUID/PGID from CODEMAN_APPDATA_PATH's owner first, via the identical owner_of() helper Start-Codeman.sh uses (parity-tested) — without it, the build used Compose's default 1000:1000 regardless of the real appdata owner (99:100 on the Unraid layout docker/README.md documents), and Start-Codeman.sh's own correctly-PUID'd build during the handoff would then rebuild those layers anyway, so the --no-cache image never actually shipped. - docker/README.md's "rebuilds ... only when it detects ... moved" wrongly described BOTH the rebuild and the volume-clearing as conditional; Start-Codeman.sh rebuilds on every start, only the volume-clearing is conditional. Corrected, and reworded around the new default. - --help/-h now prints usage and exits 0 instead of falling into the unrecognised-argument branch. - "the ONLY named volumes this stack declares" now says docker-compose.yaml specifically, since a docker-compose.override.yml could add more. New tests: PUID/PGID derivation parity with Start-Codeman.sh's owner_of(), --help handling, and — the one that actually catches blocker #1, which five source-string-matching tests did not — a real end-to-end smoke test: a synthetic deployment, a stub `docker` on PATH logging every invocation, the real script executed via a real subprocess. Confirms the real command sequence (build --no-cache, then down --volumes or plain down, then evidence the handoff genuinely ran Start-Codeman.sh) and that a working handoff fails honestly at Start-Codeman.sh's own later check rather than with EACCES. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6eadpRyqpA9PD3i139cSD
172 lines
9.9 KiB
Markdown
172 lines
9.9 KiB
Markdown
# Codeman Docker deployment
|
|
|
|
This folder contains the Compose configuration, server image Dockerfile, and environment template for a locally built Codeman server.
|
|
|
|
## Start
|
|
|
|
From the repository root, create the runtime environment file and set the required values, especially `CODEMAN_PASSWORD`.
|
|
|
|
```sh
|
|
cp docker/.env.example docker/.env
|
|
bash docker/Start-Codeman.sh
|
|
```
|
|
|
|
On PowerShell, use the following commands instead. Running Compose from inside `docker/` with no `-f` lets it discover `docker-compose.override.yml` on its own (see [Local customisation](#local-customisation)); naming the file with `-f docker/docker-compose.yaml` from the repository root silently drops the override unless it is named too.
|
|
|
|
```powershell
|
|
Copy-Item docker/.env.example docker/.env
|
|
Set-Location docker
|
|
docker compose --env-file .env up --build -d
|
|
```
|
|
|
|
Every required value is defined and explained in `.env.example`. `GEMINI_API_KEY` is intentionally optional and may remain blank.
|
|
|
|
The container starts as root so `entrypoint.sh` can correct the ownership of a bind source the Docker daemon created (it creates a missing one as `root:root`), then drops to `PUID:PGID` with `setpriv` before the server starts, so Codeman itself never runs privileged. That drop needs `cap_add: [CHOWN, DAC_OVERRIDE, KILL, SETGID, SETUID]` against the file's `cap_drop: ALL`; a compose file written elsewhere (Unraid's Compose Manager, a hand-written unit) must carry the same additions, and the entrypoint names them when they are missing. A directory owned by neither root nor `PUID:PGID` is never re-owned: it is probed for writability as the runtime account and refused with a clear message if that fails. Setting `user:` in Compose skips the whole step.
|
|
|
|
On Linux, `Start-Codeman.sh` stops with an error when required paths are missing. It creates the application-data directory when safe, detects its numeric owner as `PUID:PGID`, and detects `DOCKER_SOCKET_GID` from the configured Docker socket. It rejects a root-owned application-data directory because Codeman and its local CLI sessions must remain unprivileged.
|
|
|
|
Codeman, Claude, OpenCode, and other local sessions run as the unprivileged account named by `CODEMAN_RUNTIME_USER`, which defaults to `codeman`. When Compose is run directly, `PUID` and `PGID` default to `1000:1000`; set them in `.env` when the application-data directory has a different owner. The Bash start script determines them automatically instead.
|
|
|
|
To retain Docker-case support without root when running Compose directly, set `DOCKER_SOCKET_GID` to the numeric group ID of the host socket. On a standard Linux Docker host, obtain it with `stat -c '%g' /var/run/docker.sock`. The Bash start script detects it automatically.
|
|
|
|
## Updating
|
|
|
|
Use **App Settings → Updates** in the web UI. The checkout Compose builds from is
|
|
also mounted at `/opt/codeman`, so an update's `git checkout` and rebuild persist
|
|
on the host, and the server exiting is what restarts the container onto the new
|
|
build.
|
|
|
|
Releases that change `server.Dockerfile`, `docker-compose.yaml`, or add a key to
|
|
`.env.example` cannot be applied that way — the updater detects them, names what
|
|
changed, and asks you to run `Start-Codeman.sh` here on the host instead. Details:
|
|
[`../docs/docker-self-update.md`](../docs/docker-self-update.md).
|
|
|
|
### Major updates
|
|
|
|
`Start-Codeman.sh` rebuilds the image on every start, but only clears the
|
|
`codeman-node-modules`/`codeman-dist` build-artefact volumes when it detects
|
|
the checkout's HEAD or `package-lock.json` moved — exactly right for an
|
|
ordinary `git pull`, too narrow when a release note (or the updater's own
|
|
blocker message) calls for starting over on a Dockerfile-only change, which
|
|
touches neither. For that case, `docker/Update-Codeman.sh` force-rebuilds the
|
|
image with no layer cache, clears those two volumes, stops the stack, then
|
|
hands off to `Start-Codeman.sh` for the usual start:
|
|
|
|
```sh
|
|
bash docker/Update-Codeman.sh
|
|
```
|
|
|
|
Pass `--keep-volumes` to skip clearing them (safe only if you know the
|
|
rebuilt image's `node_modules`/`dist` did not change) — the scripted default
|
|
is the "Resetting the build artefacts" procedure in
|
|
[`../docs/docker-self-update.md`](../docs/docker-self-update.md). Those two
|
|
are the only named volumes `docker-compose.yaml` itself declares; a
|
|
`docker-compose.override.yml` could add more, and application data and case
|
|
workspaces are host bind mounts, never touched either way.
|
|
|
|
## Local customisation
|
|
|
|
Compose merges `docker-compose.override.yml` on top of `docker-compose.yaml`. Keep host-specific changes there rather than editing `docker-compose.yaml`, so this repository can be updated without losing them. Both `docker-compose.override.yml` and `docker-compose.override.yaml` are ignored by Git.
|
|
|
|
`Start-Codeman.sh` names the Compose file explicitly, which disables Compose's automatic discovery of the override file, so the script adds it back when one is present and prints the file it used. Running `docker compose` from this folder without any `-f` option finds it automatically. When passing `-f docker/docker-compose.yaml` from the repository root, add `-f docker/docker-compose.override.yml` as well, or the override is silently ignored.
|
|
|
|
An override file adds to and replaces individual settings. It cannot delete a key from `docker-compose.yaml`, and Compose concatenates rather than replaces `ports`, so removing a published port still requires editing `docker-compose.yaml`. The example below replaces the restart policy and adds a mount, leaving every other setting in place:
|
|
|
|
```yaml
|
|
services:
|
|
codeman:
|
|
restart: always
|
|
volumes:
|
|
- /srv/projects:/srv/projects
|
|
```
|
|
|
|
### Reverse-proxy host allowlist
|
|
|
|
Codeman rejects any request whose `Host` header is not on its own allowlist - a
|
|
DNS-rebinding guard, not a Compose or Docker concern. Loopback, any IP literal,
|
|
the configured `--host`, and a few tunnel-provider suffixes are allowed by
|
|
default; a reverse-proxied domain is not, and is rejected with
|
|
`403 Forbidden: host not allowed` before the request reaches any handler.
|
|
|
|
Add the domain with `CODEMAN_ALLOWED_HOSTS` in `.env`:
|
|
|
|
```sh
|
|
CODEMAN_ALLOWED_HOSTS='codeman.example.com,.internal.example.com'
|
|
```
|
|
|
|
`docker-compose.yaml` forwards it into the container (Compose only passes
|
|
through the environment keys it explicitly lists, and this is one of them, with
|
|
an empty default so the line is optional in `.env`).
|
|
|
|
See the application's own `docs/wiki/Remote-Access.md` for the full allowlist
|
|
format and the tunnel providers it accepts by default.
|
|
|
|
## Application data storage
|
|
|
|
The default configuration uses a host-folder bind mount:
|
|
|
|
```yaml
|
|
volumes:
|
|
- type: bind
|
|
source: ${CODEMAN_APPDATA_PATH}
|
|
target: /home/${CODEMAN_RUNTIME_USER}
|
|
```
|
|
|
|
Set `CODEMAN_APPDATA_PATH` in `.env` to a directory that the Docker daemon can access. The example value is `/mnt/user/appdata/codeman`.
|
|
|
|
`CODEMAN_CASES_PATH` is the separate host directory for managed case workspaces. It is mounted into Codeman at the same absolute path, allowing the host Docker daemon to bind it into an isolated case container. Set it to a child directory of `CODEMAN_APPDATA_PATH` unless you deliberately store workspaces elsewhere.
|
|
|
|
Compose also exposes `CODEMAN_APPDATA_PATH` to Codeman as `CODEMAN_DOCKER_HOST_HOME`. This lets Docker case seed files, CLI credentials and the hook secret be mounted using paths that exist in the host daemon's filesystem. Direct host installations do not set this variable and retain their existing behaviour.
|
|
|
|
Set `CODEMAN_DOCKER_DISABLE_SWAP_LIMIT=1` when `docker info` reports `SwapLimit=false`. Codeman continues to apply the configured case memory limit, omits Docker's unsupported `--memory-swap` option, and filters only the daemon's exact swap-capability warning. Every other Docker create error and its exit status remain visible.
|
|
|
|
For an existing installation created by a root-running image, change ownership of the application-data directory before upgrading so the configured `PUID` and `PGID` can read the saved credentials and state:
|
|
|
|
```sh
|
|
chown -R 99:100 /mnt/user/appdata/codeman
|
|
```
|
|
|
|
Replace `99:100` and the path with the values from your `.env` file.
|
|
|
|
Do not replace this bind mount with a Docker-managed named volume when Docker cases are enabled. Codeman passes seed, credential, transcript and hook-secret bind sources to the host Docker daemon, so their source files must have stable paths in the daemon's filesystem. A named volume does not provide the required host path mapping.
|
|
|
|
## Static macvlan networking
|
|
|
|
The default configuration publishes a host port. It does not use `network_mode: host`. To attach Codeman directly to an existing external macvlan network with a static IP address and MAC address, remove the `ports:` section from `docker-compose.yaml` and add the following to the `codeman` service. The service and network additions can instead be placed in `docker-compose.override.yml`, but the `ports:` removal cannot, as described under [Local customisation](#local-customisation):
|
|
|
|
```yaml
|
|
mac_address: ${CODEMAN_MAC_ADDRESS}
|
|
networks:
|
|
codeman_lan:
|
|
ipv4_address: ${CODEMAN_IPV4_ADDRESS}
|
|
```
|
|
|
|
Then add this top-level network declaration:
|
|
|
|
```yaml
|
|
networks:
|
|
codeman_lan:
|
|
external: true
|
|
name: ${CODEMAN_MACVLAN_NETWORK}
|
|
```
|
|
|
|
Set `CODEMAN_MACVLAN_NETWORK`, `CODEMAN_IPV4_ADDRESS`, and `CODEMAN_MAC_ADDRESS` in `.env`. The values in `.env.example` match the supplied Unraid example network and should be changed for other hosts.
|
|
|
|
### Create a managed macvlan network
|
|
|
|
If an external macvlan network does not already exist, use this top-level declaration instead. Do not use it together with the external-network declaration.
|
|
|
|
```yaml
|
|
networks:
|
|
codeman_lan:
|
|
driver: macvlan
|
|
driver_opts:
|
|
parent: ${CODEMAN_MACVLAN_PARENT}
|
|
ipam:
|
|
config:
|
|
- subnet: ${CODEMAN_MACVLAN_SUBNET}
|
|
gateway: ${CODEMAN_MACVLAN_GATEWAY}
|
|
```
|
|
|
|
Macvlan containers are ordinarily not reachable from their Docker host without additional host-network routing. Confirm the selected address, MAC address, parent interface, and subnet are reserved and valid for the target network before starting the stack.
|