This is an automated email from the ASF dual-hosted git repository.

yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new ee2eaf9ba4 [#12448] improvement(docker, mcp): Allow the MCP server 
image to run as a non-root user (#12446)
ee2eaf9ba4 is described below

commit ee2eaf9ba428737cbd7565724d1df356eb4c3111
Author: Mark Hoerth <[email protected]>
AuthorDate: Thu Aug 13 02:57:02 2026 -0700

    [#12448] improvement(docker, mcp): Allow the MCP server image to run as a 
non-root user (#12446)
    
    ### What changes were proposed in this pull request?
    
    Makes the MCP server image runnable as a non-root user, using the same
    setup the other Gravitino images already have.
    
    | Change | Detail |
    | --- | --- |
    | Move off `/root` | `/root/mcp-server` becomes `/opt/mcp-server`,
    updating `WORKDIR`, both `COPY` destinations, and the `cd` in
    `start-mcp-server.sh` |
    | Adopt the existing non-root setup | `COPY --chmod=775`, `useradd -u
    1000 -g 0 -M -s /sbin/nologin gravitino`, and `USER 1000`, as in the
    `gravitino`, `iceberg-rest-server` and `lance-rest-server` Dockerfiles |
    | Recursive `chmod` | `chmod -R 775`, rather than the non-recursive form
    those images use |
    | Pin the uv cache | `ENV UV_CACHE_DIR=/opt/mcp-server/.cache/uv` |
    
    Two of those differ from the sibling images, and both are required here
    rather than stylistic.
    
    The `chmod` is recursive because `uv venv`, `uv sync` and `uv pip
    install -e .` run after the `COPY` and create `.venv` and the uv cache
    as root at mode 755. `COPY --chmod` cannot reach them, so a
    non-recursive `chmod 775` on the top directory alone would leave both
    inaccessible to an arbitrary UID.
    
    `UV_CACHE_DIR` is needed because uv defaults its cache to
    `$HOME/.cache/uv`. A user with no passwd entry gets `HOME=/`, which is
    not writable, so even after the working directory moved the entrypoint
    still failed:
    
    ```
    error: Failed to initialize cache at `/.cache/uv`
      Caused by: failed to create directory `/.cache/uv`: Permission denied
    ```
    
    Pinning the cache inside the application directory means it is populated
    as root at build time and picked up by the same `chmod`.
    
    No application code changes, no change to the base image, no change to
    the entrypoint's behaviour beyond the paths it depends on, and no Helm
    chart changes.
    
    ### Why are the changes needed?
    
    The image could only run as root. Setting `runAsUser: 1001` on the
    container made it crash immediately:
    
    ```
    start-mcp-server.sh: line 21: cd: /root/mcp-server: Permission denied
    error: failed to open file `/root/mcp-server/uv.toml`: Permission denied 
(os error 13)
    ```
    
    `/root` is mode 0700 in the `python:3.10` base, so a process running as
    any other user cannot enter its own working directory. A `USER`
    directive alone would not have fixed it; the application had to move.
    
    Kubernetes deployments increasingly require a non-root container, and
    OpenShift enforces it: its default security context constraint assigns
    an arbitrary UID and refuses an image that needs root. Membership of gid
    0, with group permissions matching user permissions, is what makes an
    arbitrary assigned UID work, which is why the other Gravitino images are
    built that way.
    
    The Helm charts set a non-root security context on the other components.
    The MCP server is currently the exception that has to be left out.
    
    Fixed: #12448
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, two.
    
    The image runs as uid 1000 by default instead of root. A deployment that
    depends on running as root needs updating.
    
    The application lives at `/opt/mcp-server` instead of
    `/root/mcp-server`. A deployment that mounts a volume into the old path
    needs updating.
    
    No API or configuration property changes.
    
    ### How was this patch tested?
    
    Built locally and run as several users. All exited 0 and printed usage
    with no permission error:
    
    ```shell
    docker run --rm --user 1000    <image> --help
    docker run --rm --user 1001    <image> --help
    docker run --rm --user 4238721 <image> --help   # simulates OpenShift's 
arbitrary UID
    docker run --rm                <image> --help
    ```
    
    The arbitrary high UID is the case the gid 0 group ownership exists for,
    and the one a plain `--user 1000` does not exercise.
    
    `--help` alone was not treated as sufficient, since startup touches
    paths it does not. The server was also started for real in
    streamable-http mode as uid 4238721, and reached `Application startup
    complete` with no permission errors, confirming `.venv` and the uv cache
    are reachable at runtime.
    
    Inside the running container the identity is `uid=4238721 gid=0(root)`,
    and `/opt/mcp-server`, `/opt/mcp-server/.venv` and
    `/opt/mcp-server/.cache/uv` are all `drwxrwxr-x root root`.
    
    The original failure was found on a Kubernetes cluster, by patching a
    running MCP deployment with `runAsUser: 1001` and `runAsNonRoot: true`,
    which produced the CrashLoopBackOff and the two lines quoted above.
    
    Co-authored-by: Mark Hoerth <[email protected]>
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 dev/docker/mcp-server/Dockerfile          | 14 +++++++++++---
 dev/docker/mcp-server/start-mcp-server.sh |  2 +-
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/dev/docker/mcp-server/Dockerfile b/dev/docker/mcp-server/Dockerfile
index 5826f1c085..3aab0cef36 100644
--- a/dev/docker/mcp-server/Dockerfile
+++ b/dev/docker/mcp-server/Dockerfile
@@ -20,13 +20,16 @@ FROM python:3.10
 
 LABEL maintainer="[email protected]"
 
-WORKDIR /root/mcp-server
+WORKDIR /opt/mcp-server
 
 COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
 
-COPY packages/mcp-server /root/mcp-server
+COPY --chmod=775 packages/mcp-server /opt/mcp-server
 
-COPY start-mcp-server.sh /root/mcp-server
+COPY --chmod=775 start-mcp-server.sh /opt/mcp-server
+
+# uv defaults its cache to $HOME/.cache/uv, which a non-root user cannot 
create.
+ENV UV_CACHE_DIR=/opt/mcp-server/.cache/uv
 
 RUN uv venv
 
@@ -34,4 +37,9 @@ RUN uv sync
 
 RUN uv pip install -e .
 
+RUN chmod -R 775 /opt/mcp-server \
+    && useradd -u 1000 -g 0 -M -s /sbin/nologin gravitino
+
+USER 1000
+
 ENTRYPOINT ["/bin/bash", "start-mcp-server.sh"]
diff --git a/dev/docker/mcp-server/start-mcp-server.sh 
b/dev/docker/mcp-server/start-mcp-server.sh
index ed492e87b6..6947b34700 100644
--- a/dev/docker/mcp-server/start-mcp-server.sh
+++ b/dev/docker/mcp-server/start-mcp-server.sh
@@ -18,6 +18,6 @@
 # under the License.
 #
 
-cd /root/mcp-server
+cd /opt/mcp-server
 
 uv run mcp_server $@

Reply via email to