name: codeman services: codeman: build: context: .. dockerfile: docker/server.Dockerfile args: CODEMAN_RUNTIME_USER: ${CODEMAN_RUNTIME_USER} PGID: ${PGID:-1000} PUID: ${PUID:-1000} image: ${CODEMAN_IMAGE} init: true restart: unless-stopped ports: - "${CODEMAN_PORT}:${CODEMAN_PORT}" environment: # Tells the self-updater to restart by exiting (the restart policy below # relaunches it) rather than by looking for an init system that is not # here. Also set in the image; repeated so a container started without the # image default still self-identifies. CODEMAN_IN_CONTAINER: "1" # This file sets `restart: unless-stopped` below, so the updater may restart # the server by EXITING. Declared here and only here, never in the image: a # container started by plain `docker run` has no restart policy unless the # operator gave it one, and there the updater asks the daemon instead and # stages the update for a manual restart when it cannot get an answer. CODEMAN_RESTART_BY_EXIT: "1" CODEMAN_DOCKER_BRIDGE_HOOKS: ${CODEMAN_DOCKER_BRIDGE_HOOKS} # Host-side equivalent of the runtime user's HOME. Docker case seed, # credential and hook mounts are translated into the daemon namespace. CODEMAN_DOCKER_HOST_HOME: ${CODEMAN_APPDATA_PATH} CODEMAN_DOCKER_DISABLE_SWAP_LIMIT: ${CODEMAN_DOCKER_DISABLE_SWAP_LIMIT} CODEMAN_CASES_PATH: ${CODEMAN_CASES_PATH} CODEMAN_HOST: ${CODEMAN_HOST} CODEMAN_PASSWORD: ${CODEMAN_PASSWORD} CODEMAN_PORT: ${CODEMAN_PORT} CODEMAN_USERNAME: ${CODEMAN_USERNAME} GEMINI_API_KEY: ${GEMINI_API_KEY} PGID: ${PGID:-1000} PUID: ${PUID:-1000} TZ: ${TZ} group_add: # Retain access to the host Docker socket without running as root. - ${DOCKER_SOCKET_GID:-999} volumes: # Application data and CLI credentials persist on the configured host # path, rather than in a Docker-managed volume. - type: bind source: ${CODEMAN_APPDATA_PATH} target: /home/${CODEMAN_RUNTIME_USER} # Docker cases are sibling containers on the host daemon. Their workspace # must be visible to Codeman at the same absolute path used by that daemon. - type: bind source: ${CODEMAN_CASES_PATH} target: ${CODEMAN_CASES_PATH} # Codeman uses the host daemon to create isolated Docker cases. This is # Docker-outside-of-Docker, not Docker-in-Docker. - type: bind source: ${DOCKER_SOCKET} target: /var/run/docker.sock # The application source, so App Settings -> Updates can update in place. # This is the SAME checkout used as the build context above, mounted over # the image's baked copy: a `git checkout` performed inside the container # then lands on the host and survives the container being recreated. # Without it the pull would go to the container's writable layer and be # silently discarded by the next `up`. See docs/docker-self-update.md. # Defaults to `..` — the build context above — which Compose resolves # against the project directory, so plain `docker compose up` works with # no extra configuration. Set CODEMAN_REPO_PATH only to point elsewhere. - type: bind source: ${CODEMAN_REPO_PATH:-..} target: /opt/codeman # Build artefacts live in named volumes layered OVER the repo bind mount, # so `npm install` and `npm run build` inside the container never write # into the host checkout. That keeps container-compiled native modules # (node-pty is built from source here) out of a checkout that may also be # used to run Codeman natively, and keeps `git status` clean. Docker seeds # an EMPTY named volume from the image, so the first start inherits the # image's already-built node_modules and dist rather than paying for a # bootstrap build. - type: volume source: codeman-node-modules target: /opt/codeman/node_modules - type: volume source: codeman-dist target: /opt/codeman/dist extra_hosts: - "host.docker.internal:host-gateway" security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: # The entrypoint corrects bind-mount ownership as root before dropping to # PUID:PGID. Everything not listed here remains dropped by cap_drop above. - CHOWN - DAC_OVERRIDE - SETGID - SETUID healthcheck: test: - CMD-SHELL - >- node -e "fetch('http://127.0.0.1:${CODEMAN_PORT}/api/status').then((response) => process.exit(response.status < 500 ? 0 : 1)).catch(() => process.exit(1))" interval: 30s timeout: 5s retries: 3 start_period: 30s volumes: # Container-owned build artefacts. They persist across container recreation, # so an in-app update's `npm install` output is not thrown away by the next # `up`, and they are seeded from the image on first use. Removing them (or # `docker compose down -v`) is the supported reset: the next start rebuilds # from the image. codeman-node-modules: codeman-dist: