fix(docker): actually run all seven servers as their non-root user - #4783
Open
Ashfaqbs wants to merge 1 commit into
Open
fix(docker): actually run all seven servers as their non-root user#4783Ashfaqbs wants to merge 1 commit into
Ashfaqbs wants to merge 1 commit into
Conversation
The three Python images (fetch, git, time) create an app user and --chown the venv to it, but never switch to it with USER, so the useradd/--chown lines were dead and every process still ran as root. The four Node images inherit root from node:22-alpine despite that base image already shipping a non-root node user. Adding USER app/USER node alone isn't enough, though: /root defaults to 700, and each Python venv's interpreter is a symlink into /root/.local/share/uv/python/.../bin, resolved on every startup, not just at build time — so switching users without loosening that permission breaks the interpreter outright. Fixed by chmod o+rx /root before the USER switch, verified by actually building and running all three Python images (git also needed a wildcard safe.directory, since git now runs as a different uid than whatever host directory gets bind-mounted in). Verified by building and exercising each image (id, plus a real MCP initialize handshake; for git, a real git_status against a bind-mounted repo owned by a different uid). The four Node images could not be build-tested locally due to an unrelated, pre-existing npm install failure reproducible on main (filed as modelcontextprotocol#4782); the USER node addition mirrors the exact fix already verified working on the Python images and matches the pattern Node's own official images document, and for memory specifically also chowns dist/ first since that's where the knowledge graph JSONL persists by default. Fixes modelcontextprotocol#4741
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Since #2205, the three Python Dockerfiles (
fetch,git,time) create anappuser and--chownthe copied venv to it, but never actually switch to it withUSER— so those lines are dead code and every container still runs as UID 0. The four Node images (everything,filesystem,memory,sequentialthinking) inherit root fromnode:22-alpineand never use the non-rootnodeuser that base image already ships.Adding
USER app/USER nodeisn't sufficient by itself, though — I found two things that would otherwise break these servers the moment they stop running as root:/rootdefaults to700. Each Python venv'spythonbinary is a symlink into/root/.local/share/uv/python/.../bin/python3.11, resolved at every process startup, not just at build time. A non-root user can't even traverse into/rootto reach it, so the interpreter itself fails (Could not find platform independent libraries...) — not just a file-permission error somewhere incidental. Fixed withchmod o+rx /rootbefore theUSERswitch.gitnow runs as a different uid than whatever repo gets bind-mounted in. Modern git refuses to operate on a directory it doesn't consider "safe" when the owning uid doesn't match the calling user. Since this container's only job is operating on whatever repo is mounted into it, there's no meaningful "wrong owner" to protect against here, so I addedgit config --system --add safe.directory '*'.memorygets one more line: its defaultMEMORY_FILE_PATHisdist/memory.jsonl, right next to the compiled entrypoint —dist/was just copied in as root, so Ichown -R node:node /app/distbefore the switch, otherwise the very first write to the default path fails.Fixes #4741
Server Details
Motivation and Context
Containers built from these Dockerfiles run as root despite already carrying most of the scaffolding to run as non-root, which fails Kubernetes
runAsNonRootadmission policies outright and, forfilesystem, makeswrite_file/create_directorycreate host files the invoking user then can't edit withoutsudo.How Has This Been Tested?
Built and ran the three Python images directly (
fetch,git,time) — confirmedidreports the non-root user in each, and drove a real MCPinitializehandshake over stdio against each one to confirm the server actually starts and responds (not just "doesn't crash immediately"). Forgitspecifically, went further: bind-mounted a real host repo (owned by a different uid than the container'sappuser, as it always will be on a real machine) and called thegit_statustool through a full MCP round-trip — confirmed it correctly reports the working tree, proving thesafe.directoryfix actually resolves the dubious-ownership rejection rather than just not crashing on startup.I could not build-test the four Node images locally —
npm installin their builder stage currently fails on unmodifiedmaintoo (confirmed via a from-scratchgit archivebuild with a fully cleared build cache), withCannot read properties of null (reading 'edgesOut'); filed separately as #4782 with a bisection down to thedevDependenciesset, since it isn't something this PR touches or introduces. For those four,USER nodemirrors the identical pattern verified working on the Python images and is the pattern Node's own official images document;hadolintreports no new errors or warnings against any of the seven Dockerfiles from this change (only the expected informational note aboutnode/appbeing a name rather than a numeric UID).Breaking Changes
None for typical usage. Anyone relying on the (undocumented, unintended) root behavior — e.g. writing outside directories the non-root user can reach — would need to adjust, but that's exactly the behavior #4741 asks to close.
Types of changes
Checklist
initialize/tool-call round trips, described above)Additional context
Filed #4782 for the unrelated
npm installfailure this surfaced while verifying the Node images.🤖 Generated with Claude Code