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 $@