mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
Compose binds CODEMAN_APPDATA_PATH and CODEMAN_CASES_PATH from the host. When either path does not exist yet - a first run, a cleared application-data directory, a restored backup - the Docker daemon creates it owned by root. The server runs unprivileged as CODEMAN_RUNTIME_USER, so it cannot create its own state directory, and the container restarts forever on: Failed to start web server: EACCES: permission denied, mkdir '/home/<user>/.codeman' Start-Codeman.sh already worked around this by preparing the directory on the host, so the failure only appears when Compose is run directly, which the README documents as a supported path. Add docker/entrypoint.sh, which starts as root, corrects the ownership of both bind mounts, then drops to PUID:PGID with setpriv. The Dockerfile's USER instruction is replaced by that entrypoint and CMD is unchanged. docker-compose.yaml adds back only the four capabilities the chown and the privilege drop require, so cap_drop: ALL continues to remove everything else. Two guards keep existing deployments working: - A container started with an explicit `user:` is left alone. The entrypoint execs straight through, with no elevation and no chown. - A chown that fails is a warning, not an error. Bind mounts backed by NFS, CIFS or a rootless daemon can refuse chown while remaining perfectly writable, and those deployments must keep starting. PUID and PGID are also exported as runtime environment defaults so the image behaves correctly when run without Compose, rather than depending on build args alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
119 lines
5.1 KiB
YAML
119 lines
5.1 KiB
YAML
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:
|