# syntax=docker/dockerfile:1 # ---- Build stage ------------------------------------------------------------- # Uses the uv image to resolve and install the pinned dependency set into a # self-contained virtual environment. FROM ghcr.io/astral-sh/uv:python3.13-alpine3.23 AS builder ENV UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ UV_PYTHON_DOWNLOADS=never WORKDIR /app # Dependencies first: this layer only rebuilds when pyproject.toml/uv.lock change. COPY pyproject.toml uv.lock .python-version ./ RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-dev --no-install-project # Then install the project itself as a regular wheel. COPY README.md ./ COPY src ./src RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-dev --no-editable # ---- Runtime stage ----------------------------------------------------------- FROM python:3.13-alpine3.23 AS runtime ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PATH="/app/.venv/bin:$PATH" \ MCP_TRANSPORT=http \ MCP_HOST=0.0.0.0 \ MCP_PORT=8000 RUN addgroup -S -g 1001 xmcp \ && adduser -S -D -H -u 1001 -G xmcp xmcp WORKDIR /app COPY --from=builder --chown=xmcp:xmcp /app/.venv /app/.venv USER xmcp EXPOSE 8000 # In HTTP mode, probe the MCP port; stdio containers are always healthy. HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD ["python", "-c", "import os, socket, sys\nif os.environ.get('MCP_TRANSPORT', 'stdio') != 'http':\n sys.exit(0)\ns = socket.socket()\ns.settimeout(3)\nsys.exit(s.connect_ex(('127.0.0.1', int(os.environ.get('MCP_PORT', '8000')))))"] CMD ["python", "-m", "xmcp"]