51 lines
2.8 KiB
Docker
51 lines
2.8 KiB
Docker
# Debian and not Alpine, deliberately. A multi-toolchain image on musl means pip cannot use the
|
|
# manylinux wheels that almost every Python package ships, so it falls back to building from source
|
|
# and needs a compiler toolchain to do it - a larger image than the one this choice costs, reached
|
|
# by a longer road. Node's own native modules have the same problem.
|
|
FROM node:24-bookworm-slim
|
|
|
|
# The JDK comes from the image that builds it rather than from an apt repository, so the version is
|
|
# the one the project is written for instead of whatever the distribution happens to carry.
|
|
COPY --from=eclipse-temurin:25-jdk /opt/java/openjdk /opt/java/openjdk
|
|
ENV JAVA_HOME=/opt/java/openjdk
|
|
ENV PATH="/opt/java/openjdk/bin:${PATH}"
|
|
|
|
# python3 and pip, and nothing that compiles: a project needing a compiler is a project whose
|
|
# dependencies are not prebuilt, which is a decision for an operator to take knowingly.
|
|
# git is here because a lockfile may name a git dependency; curl because ./mvnw fetches with it.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
python3 python3-pip python3-venv ca-certificates git curl \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& ln -sf /usr/bin/python3 /usr/local/bin/python \
|
|
&& ln -sf /usr/bin/pip3 /usr/local/bin/pip
|
|
|
|
# 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.
|
|
|
|
# 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
|
|
# 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_UID}:${MCP_GID}
|
|
CMD ["node", "src/index.js"]
|