Let the worker write to its own volumes
A named volume whose mount point is absent from the image is created owned by root, so the worker - which drops root - could not have written a single copy into /instances. The failure would have surfaced at the first start_service, as a refused execution far from the line that caused it. Creating the two directories in the image and giving them to the runtime user is what makes the volume inherit that ownership. The user id follows as a build argument, because one number has to satisfy three things at once: who the process is, who owns the volumes, and who owns the workspace directory on the host. Left hardcoded, changing MCP_UID to match a deployment's own user would have brought the same fault back. The guard around useradd is for the ordinary case of pointing it at an id the base image already uses - 1000 is the node user - which is otherwise a build failure. Verified by building both ways and writing into a fresh volume as 10001 and as 1000, and by checking the image carries node 24, Python 3.11 and OpenJDK 25. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b76642a09b
commit
d19fa7ba4e
|
|
@ -23,15 +23,28 @@ RUN apt-get update \
|
|||
# No Maven. A Spring project carries ./mvnw, which pins the Maven version the project is built
|
||||
# with; installing a second one here would only give an agent a way to use the wrong one.
|
||||
|
||||
RUN groupadd -g 10001 mcp \
|
||||
&& useradd -r -u 10001 -g mcp -M -d /nonexistent mcp \
|
||||
&& mkdir -p /tmp/dev-server \
|
||||
&& chown -R mcp:mcp /tmp/dev-server
|
||||
# One number decides three things that must agree: who the process is, who owns the volumes, and
|
||||
# who owns the workspace directory on the host. Passing it in as a build argument is what keeps
|
||||
# them from drifting apart - a mismatch here is only discovered when an execution cannot write.
|
||||
ARG MCP_UID=10001
|
||||
ARG MCP_GID=10001
|
||||
RUN set -eux; \
|
||||
if ! getent group "${MCP_GID}" >/dev/null; then groupadd -g "${MCP_GID}" mcp; fi; \
|
||||
if ! getent passwd "${MCP_UID}" >/dev/null; then useradd -r -u "${MCP_UID}" -g "${MCP_GID}" -M -d /nonexistent mcp; fi; \
|
||||
mkdir -p /tmp/dev-server /instances /npm-cache; \
|
||||
chown -R "${MCP_UID}:${MCP_GID}" /tmp/dev-server /instances /npm-cache
|
||||
|
||||
# /instances and /npm-cache exist here only so the named volumes mounted over them inherit this
|
||||
# ownership. A volume whose mount point is absent from the image is created root-owned, and a
|
||||
# container that drops root then cannot write a single byte into it - which surfaces as a refused
|
||||
# start on the first execution, far from the line that caused it.
|
||||
|
||||
WORKDIR /app
|
||||
COPY --chown=mcp:mcp package.json ./
|
||||
COPY --chown=mcp:mcp src ./src
|
||||
# By number, not by name: with MCP_UID set to an id the base image already uses, the mcp user
|
||||
# is never created and a copy chowned to it would fail the build.
|
||||
COPY --chown=${MCP_UID}:${MCP_GID} package.json ./
|
||||
COPY --chown=${MCP_UID}:${MCP_GID} src ./src
|
||||
|
||||
ENV NODE_ENV=production
|
||||
USER mcp
|
||||
USER ${MCP_UID}:${MCP_GID}
|
||||
CMD ["node", "src/index.js"]
|
||||
|
|
|
|||
|
|
@ -29,12 +29,15 @@ services:
|
|||
dev-server-mcp:
|
||||
build:
|
||||
context: ./dev-server-mcp
|
||||
args:
|
||||
MCP_UID: ${MCP_UID:-10001}
|
||||
MCP_GID: ${MCP_GID:-10001}
|
||||
command: ["node", "src/index.js"]
|
||||
environment:
|
||||
DEV_SERVER_MCP_API_KEYS: ${DEV_SERVER_MCP_API_KEYS}
|
||||
DEV_SERVER_WORKER_TOKEN: ${DEV_SERVER_WORKER_TOKEN}
|
||||
DEV_SERVER_WORKER_URL: http://dev-server-worker:4000
|
||||
user: "10001:10001"
|
||||
user: "${MCP_UID:-10001}:${MCP_GID:-10001}"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,noexec,nosuid,size=64m
|
||||
|
|
@ -52,6 +55,9 @@ services:
|
|||
dev-server-worker:
|
||||
build:
|
||||
context: ./dev-server-mcp
|
||||
args:
|
||||
MCP_UID: ${MCP_UID:-10001}
|
||||
MCP_GID: ${MCP_GID:-10001}
|
||||
command: ["node", "src/worker-index.js"]
|
||||
environment:
|
||||
DEV_SERVER_WORKER_TOKEN: ${DEV_SERVER_WORKER_TOKEN}
|
||||
|
|
@ -155,7 +161,7 @@ services:
|
|||
|
||||
mcp-gateway:
|
||||
image: caddy:2
|
||||
user: "10001:10001"
|
||||
user: "${MCP_UID:-10001}:${MCP_GID:-10001}"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
# /config only. /data is a volume instead, because Caddy keeps the certificate and its ACME
|
||||
|
|
|
|||
Loading…
Reference in New Issue