diff --git a/backend/Dockerfile b/backend/Dockerfile index f4063d7ae..b3e1aea36 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,10 +1,14 @@ -# Backend Development Dockerfile +# Backend Dockerfile — multi-stage build +# Stage 1 (builder): compiles native Python extensions with build-essential +# Stage 2 (dev): retains toolchain for dev containers (uv sync at startup) +# Stage 3 (runtime): clean image without compiler toolchain for production # UV source image (override for restricted networks that cannot reach ghcr.io) ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.7.20 FROM ${UV_IMAGE} AS uv-source -FROM python:3.12-slim-bookworm +# ── Stage 1: Builder ────────────────────────────────────────────────────────── +FROM python:3.12-slim-bookworm AS builder ARG NODE_MAJOR=22 ARG APT_MIRROR @@ -16,7 +20,7 @@ RUN if [ -n "${APT_MIRROR}" ]; then \ sed -i "s|deb.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list 2>/dev/null || true; \ fi -# Install system dependencies + Node.js (provides npx for MCP servers) +# Install build tools + Node.js (build-essential needed for native Python extensions) RUN apt-get update && apt-get install -y \ curl \ build-essential \ @@ -29,6 +33,41 @@ RUN apt-get update && apt-get install -y \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* +# Install uv (source image overridable via UV_IMAGE build arg) +COPY --from=uv-source /uv /uvx /usr/local/bin/ + +# Set working directory +WORKDIR /app + +# Copy backend source code +COPY backend ./backend + +# Install dependencies with cache mount +RUN --mount=type=cache,target=/root/.cache/uv \ + sh -c "cd backend && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync" + +# ── Stage 2: Dev ────────────────────────────────────────────────────────────── +# Retains compiler toolchain from builder so startup-time `uv sync` can build +# source distributions in development containers. +FROM builder AS dev + +# Install Docker CLI (for DooD: allows starting sandbox containers via host Docker socket) +COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker + +EXPOSE 8001 2024 + +CMD ["sh", "-c", "cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001"] + +# ── Stage 3: Runtime ────────────────────────────────────────────────────────── +# Clean image without build-essential — reduces size (~200 MB) and attack surface. +FROM python:3.12-slim-bookworm + +# Copy Node.js runtime from builder (provides npx for MCP servers) +COPY --from=builder /usr/bin/node /usr/bin/node +COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules +RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/bin/npm \ + && ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/bin/npx + # Install Docker CLI (for DooD: allows starting sandbox containers via host Docker socket) COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker @@ -38,12 +77,8 @@ COPY --from=uv-source /uv /uvx /usr/local/bin/ # Set working directory WORKDIR /app -# Copy frontend source code -COPY backend ./backend - -# Install dependencies with cache mount -RUN --mount=type=cache,target=/root/.cache/uv \ - sh -c "cd backend && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync" +# Copy backend with pre-built virtualenv from builder +COPY --from=builder /app/backend ./backend # Expose ports (gateway: 8001, langgraph: 2024) EXPOSE 8001 2024 diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index 8f5c89b4d..c0749ba9d 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -113,6 +113,7 @@ services: build: context: ../ dockerfile: backend/Dockerfile + target: dev # cache_from disabled - requires manual setup: mkdir -p /tmp/docker-cache-gateway args: APT_MIRROR: ${APT_MIRROR:-} @@ -169,6 +170,7 @@ services: build: context: ../ dockerfile: backend/Dockerfile + target: dev # cache_from disabled - requires manual setup: mkdir -p /tmp/docker-cache-langgraph args: APT_MIRROR: ${APT_MIRROR:-}