# syntax=docker/dockerfile:1

# Base images are overridable so the image can be built against whatever
# hardened registry a restricted environment mandates (Docker Hardened Images,
# Chainguard, an internal mirror, ...). Defaults are what CI builds and scans.
#   BUILDER_IMAGE  needs a shell + glibc — uv installs a glibc standalone CPython.
#                  e.g. dhi.io/python:3-dev
#   RUNTIME_IMAGE  needs glibc + libgcc + libstdc++ + libssl/libffi/libz +
#                  ca-certificates, and a non-root user; shell-less is ideal.
#                  e.g. dhi.io/python:3 (its own Python is unused — the app runs
#                  on the standalone CPython copied from the builder).
# See docs/operations.md "Restricted / hardened-registry environments".
ARG BUILDER_IMAGE=python:3.14-slim-trixie
ARG RUNTIME_IMAGE=gcr.io/distroless/cc-debian13:nonroot
ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.12.5

# Pinned uv as its own stage so the binary source is overridable (UV_IMAGE) —
# e.g. a local scratch image built from the uv release tarball if ghcr.io is
# unreachable.
FROM ${UV_IMAGE} AS uv

# ---------------------------------------------------------------------------
# Builder base: everything shared regardless of which extras end up installed
# — the standalone CPython (python-build-standalone, via uv) and the source
# tree. Split from the actual `uv sync` so that step alone forks per target.
# ---------------------------------------------------------------------------
FROM ${BUILDER_IMAGE} AS builder-base

# Pull the latest point-release fixes into the (throwaway) build stage. Skipped
# automatically on a base with no apt (e.g. an already-patched hardened image).
RUN if command -v apt-get >/dev/null 2>&1; then \
        apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/*; \
    fi

COPY --from=uv /uv /usr/local/bin/uv

ENV UV_PYTHON_INSTALL_DIR=/opt/python \
    UV_PYTHON_PREFERENCE=only-managed \
    UV_LINK_MODE=copy \
    UV_COMPILE_BYTECODE=1

WORKDIR /app

# Install a standalone CPython, then drop its bundled pip / ensurepip — the
# runtime never installs packages, and pip's _vendor tree is the only thing
# dragging known-vulnerable msgpack / setuptools copies into the image.
RUN uv python install 3.13 \
 && rm -rf /opt/python/*/lib/python3.13/site-packages/pip \
           /opt/python/*/lib/python3.13/site-packages/pip-*.dist-info \
           /opt/python/*/lib/python3.13/ensurepip \
           /opt/python/*/lib/python3.13/test \
           /opt/python/*/lib/python3.13/idlelib

COPY pyproject.toml uv.lock ./
COPY src/ src/
COPY README.md LICENSE NOTICE ./
# Vendor the embedding model so the runtime never calls huggingface.co on first
# use — needed by every role (`remember`/`recall` embed just as much as
# `search` does). fetch_model.py pulls just the ~87 MB of PyTorch + tokenizer
# files sentence-transformers needs, into /app/models/all-MiniLM-L6-v2/.
COPY scripts/fetch_model.py scripts/fetch_model.py

# ---------------------------------------------------------------------------
# Full builder: the parser stack (Markdown/Python/YAML/PDF) + ingestion.
# Backs both the default (--role all) image and the `knowledge` target — they
# need the identical dependency set, differing only in which tools their CMD
# exposes.
# ---------------------------------------------------------------------------
FROM builder-base AS builder-full
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --extra pdf --no-dev --frozen --no-install-project --no-editable
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --extra pdf --no-dev --frozen --no-editable
RUN /app/.venv/bin/python scripts/fetch_model.py

# ---------------------------------------------------------------------------
# Memory builder: the embedder + memory module only — no parser stack, no
# pymupdf, no watchdog-driven `ingest --watch` use. Backs the `memory` target.
# ---------------------------------------------------------------------------
FROM builder-base AS builder-memory
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --no-dev --frozen --no-install-project --no-editable
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --no-dev --frozen --no-editable
RUN /app/.venv/bin/python scripts/fetch_model.py

# ---------------------------------------------------------------------------
# Runtime: needs glibc + libgcc + libstdc++ (torch's C++ runtime) + libssl /
# libffi / libz (the standalone CPython's stdlib) + ca-certificates, a
# non-root user, and ideally no shell or package manager. distroless "cc"
# (the default) and dhi.io/python:3 both fit — both run as uid 65532.
#
# Three targets, all sharing this same shape:
#   knowledge  full deps; CMD pins --role knowledge. For the split-deployment
#              docker-compose.knowledge.yml.
#   memory     slim deps (builder-memory); CMD pins --role memory. For
#              docker-compose.memory.yml.
#   (default)  full deps, CMD has no --role (defaults to "all"). This is the
#              LAST stage in the file, so a plain `docker build .` — what
#              docker-compose.yml uses — resolves here, unchanged from before
#              these targets existed.
# ---------------------------------------------------------------------------
FROM ${RUNTIME_IMAGE} AS knowledge
COPY --from=builder-full /opt/python /opt/python
# Numeric uid/gid (not the "nonroot" name) so the chown resolves on any base.
COPY --from=builder-full --chown=65532:65532 /app/.venv /app/.venv
# SentenceTransformerEmbedder resolves /opt/models/all-MiniLM-L6-v2 before
# falling back to the Hub.
COPY --from=builder-full --chown=65532:65532 /app/models /opt/models
ENV PATH="/app/.venv/bin:${PATH}"
WORKDIR /app
EXPOSE 8765
USER 65532
ENTRYPOINT ["/app/.venv/bin/grag-mcp"]
CMD ["serve-mcp", "--role", "knowledge"]

FROM ${RUNTIME_IMAGE} AS memory
COPY --from=builder-memory /opt/python /opt/python
COPY --from=builder-memory --chown=65532:65532 /app/.venv /app/.venv
COPY --from=builder-memory --chown=65532:65532 /app/models /opt/models
ENV PATH="/app/.venv/bin:${PATH}"
WORKDIR /app
EXPOSE 8765
USER 65532
ENTRYPOINT ["/app/.venv/bin/grag-mcp"]
CMD ["serve-mcp", "--role", "memory"]

FROM ${RUNTIME_IMAGE}
COPY --from=builder-full /opt/python /opt/python
COPY --from=builder-full --chown=65532:65532 /app/.venv /app/.venv
COPY --from=builder-full --chown=65532:65532 /app/models /opt/models
ENV PATH="/app/.venv/bin:${PATH}"
WORKDIR /app
EXPOSE 8765
USER 65532
ENTRYPOINT ["/app/.venv/bin/grag-mcp"]
CMD ["serve-mcp"]
