Docker compose: mount docker.sock + install Docker CLI so Cookbook can reach sibling containers

Cookbook now needs to docker-exec into ollama-rocm (and any other sibling
container holding a model server) from inside its own container, so:

- Dockerfile installs the Docker CLI from the static binary tarball
  (the Debian docker.io package ships dockerd but not the client on slim)
- docker-compose.yml bind-mounts /var/run/docker.sock and adds group_add
  for the host docker group (default GID 963)
- entrypoint.sh detects the socket GID, creates a local group with that
  GID, and runs usermod -aG before gosu-dropping to the app user so the
  supplementary group propagates through (gosu strips by default)
This commit is contained in:
pewdiepie-archdaemon
2026-06-19 00:32:47 +00:00
parent d70c00e8d2
commit b3e186746a
3 changed files with 58 additions and 2 deletions
+17
View File
@@ -20,6 +20,23 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \
&& rm -rf /var/lib/apt/lists/*
# Docker CLI (client only — daemon stays on the host via the
# /var/run/docker.sock mount). The Debian `docker.io` package ships
# dockerd but not the client binary on slim, so grab the static client
# tarball from download.docker.com instead.
ARG DOCKER_CLI_VERSION=27.5.1
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) DARCH=x86_64 ;; \
arm64) DARCH=aarch64 ;; \
*) echo "unsupported arch $ARCH"; exit 1 ;; \
esac \
&& curl -fsSL "https://download.docker.com/linux/static/stable/${DARCH}/docker-${DOCKER_CLI_VERSION}.tgz" \
-o /tmp/docker.tgz \
&& tar -xzf /tmp/docker.tgz -C /tmp \
&& install -m 0755 /tmp/docker/docker /usr/local/bin/docker \
&& rm -rf /tmp/docker /tmp/docker.tgz
WORKDIR /app
# Install Python deps first (layer cache). Optional extras (PyMuPDF AGPL, etc.)
+10
View File
@@ -16,6 +16,16 @@ services:
# land under /app/.local for the odysseus user. Persist them so a
# container recreate does not silently remove installed serve engines.
- ${APP_DATA_DIR:-./data}/local:/app/.local:z
# Docker socket — lets Cookbook launch commands like
# `docker exec ollama-rocm ollama show <tag>` reach the host's
# Docker daemon (and sibling containers like ollama-rocm /
# ollama-test). The in-container user needs to be in the
# socket's owning group — see `group_add` below; the GID
# there must match the host's `docker` group (defaults to 963
# on Debian, 999 on Ubuntu — override via env if yours differs).
- /var/run/docker.sock:/var/run/docker.sock
group_add:
- "${DOCKER_GID:-963}"
extra_hosts:
# Lets the container reach local services on the Docker host, including
# Ollama at http://host.docker.internal:11434.
+31 -2
View File
@@ -24,6 +24,31 @@ if ! getent passwd "$PUID" >/dev/null 2>&1; then
useradd -u "$PUID" -g "$PGID" -M -s /bin/sh -d /app odysseus
fi
# Docker-socket group plumbing. When /var/run/docker.sock is bind-
# mounted (cookbook uses `docker exec` to reach sibling containers
# like ollama-rocm), the socket is owned by root:docker on the host.
# We need the in-container odysseus user to be in the matching group
# so `gosu PUID:PGID` doesn't strip it. compose's `group_add` only
# applies to the initial root process — gosu drop resets supplementary
# groups — so detect the socket's GID here and add the user via the
# username form `gosu odysseus` below.
DOCKER_SOCK="${DOCKER_SOCK:-/var/run/docker.sock}"
if [ -S "$DOCKER_SOCK" ]; then
SOCK_GID="$(stat -c '%g' "$DOCKER_SOCK" 2>/dev/null || echo '')"
if [ -n "$SOCK_GID" ] && [ "$SOCK_GID" != "0" ]; then
# Create the group locally if missing, then add odysseus to it.
if ! getent group "$SOCK_GID" >/dev/null 2>&1; then
groupadd -g "$SOCK_GID" docker_host || true
fi
SOCK_GROUP="$(getent group "$SOCK_GID" | cut -d: -f1)"
if [ -n "$SOCK_GROUP" ]; then
ODY_USER="$(getent passwd "$PUID" | cut -d: -f1)"
[ -z "$ODY_USER" ] && ODY_USER=odysseus
usermod -aG "$SOCK_GROUP" "$ODY_USER" 2>/dev/null || true
fi
fi
fi
# Repair ownership on every writable path the app touches at runtime.
#
# Bind-mounted dirs (/app/data, /app/logs) are the obvious ones, but
@@ -83,9 +108,13 @@ export PATH="/app/.local/bin:$PATH"
# Run first-time setup as the app user so data/ files get the right ownership.
# setup.py is idempotent — skips auth.json / .env if they already exist.
# || true so a setup failure never prevents the container from starting.
gosu "$PUID:$PGID" python /app/setup.py || true
# Use the username form (no :GID) so supplementary groups from /etc/group
# (including the docker-socket group set above) flow through to the child.
ODY_USER="$(getent passwd "$PUID" | cut -d: -f1)"
[ -z "$ODY_USER" ] && ODY_USER="$PUID:$PGID"
gosu "$ODY_USER" python /app/setup.py || true
# Drop root and run the actual app. `gosu` is preferred over `su` /
# `sudo` because it cleans up the process tree (no extra shell layer)
# so signals (SIGTERM from `docker stop`) reach uvicorn directly.
exec gosu "$PUID:$PGID" "$@"
exec gosu "$ODY_USER" "$@"