ARG BASE_IMAGE=python:3.12-alpine
ARG BUILDER_IMAGE=${BASE_IMAGE}
ARG PRODUCTION_IMAGE=${BASE_IMAGE}
FROM ${BUILDER_IMAGE} AS builder
COPY --from=ghcr.io/astral-sh/uv:0.5.13@sha256:ea61e006cfec0e8d81fae901ad703e09d2c6cf1aa58abcb6507d124b50286f9f /uv /uvx /usr/local/bin/
ENV UV_COMPILE_BYTECODE=1
WORKDIR /app
# Copy workspace configuration and all packages
COPY pyproject.toml uv.lock ./
COPY packages/ ./packages/
COPY src/ ./src/
COPY README.md LICENSE ./
# Sync workspace packages (--no-editable makes .venv self-contained)
RUN uv sync --frozen --no-cache --no-dev --no-editable
FROM ${PRODUCTION_IMAGE} AS production
# Install runtime dependencies based on base image
# Alpine uses apk, Wolfi/Chainguard use apk, Debian uses apt
RUN if command -v apk >/dev/null 2>&1; then \
apk upgrade --no-cache && \
apk add --no-cache ca-certificates libgcc; \
elif command -v apt-get >/dev/null 2>&1; then \
apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends ca-certificates && \
rm -rf /var/lib/apt/lists/*; \
fi
# Remove pip and setuptools to eliminate CVE-2025-8869 and CVE-2026-1703
# We use uv for all package management, so pip is not needed at runtime
# Use rm -rf directly instead of invoking pip to avoid executing a potentially vulnerable binary
RUN rm -rf /usr/local/lib/python3.12/site-packages/pip* \
/usr/local/lib/python3.12/site-packages/setuptools* \
/usr/local/lib/python3.12/site-packages/wheel* \
/usr/local/bin/pip* 2>/dev/null || true
# Create non-root user for security
RUN addgroup -g 1000 ra-mcp && \
adduser -u 1000 -G ra-mcp -s /bin/sh -D ra-mcp
WORKDIR /app
# Copy only what's needed at runtime (--chown avoids a separate chown layer)
# With --no-editable, .venv is self-contained — no need to copy src/ or packages/
COPY --from=builder --chown=ra-mcp:ra-mcp /app/.venv /app/.venv
COPY --chown=ra-mcp:ra-mcp assets/index.html ./assets/index.html
COPY --chown=ra-mcp:ra-mcp resources/ ./resources/
RUN mkdir -p /app/data && chown ra-mcp:ra-mcp /app /app/data
USER ra-mcp
ENV PATH="/app/.venv/bin:$PATH"
ENV GRADIO_SERVER_NAME="0.0.0.0"
# Health check via /health endpoint
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "from urllib.request import urlopen; urlopen('http://localhost:7860/health')" || exit 1
EXPOSE 7860
CMD ["ra-serve", "--http", "--host", "0.0.0.0", "--port", "7860"]