# CPU image for the hosted playground: the FastAPI app, its React UI, and the
# pipeline stages the upload job runs as `uv run quber ...` subprocesses. The
# GPU docling parse never runs in this image; the upload job hands it to the
# RunPod worker. Build context is the repo root:
#   docker build -f deploy/playground.Dockerfile -t quber-playground .
FROM python:3.13-slim AS base

COPY --from=ghcr.io/astral-sh/uv:0.5.11 /uv /uvx /bin/

# UV_NO_SYNC: the upload job runs its stages as `uv run quber ...`; without it
# uv would re-sync the project into the environment on every run, which the
# task's unprivileged user cannot do to a read-only app tree.
ENV UV_LINK_MODE=copy \
    UV_COMPILE_BYTECODE=1 \
    UV_NO_CACHE=1 \
    UV_NO_SYNC=1 \
    PYTHONUNBUFFERED=1 \
    PATH="/app/.venv/bin:$PATH"

# Same runtime deps and the same exhaustive font set as deploy/Dockerfile, for
# the same reason: the set-of-mark table path rasterizes pages, and font
# substitution on the slim base would shift the grid and the images the
# vetting model reads away from what the developer host produces.
RUN apt-get update && apt-get install -y --no-install-recommends \
    ghostscript \
    poppler-utils \
    libgl1 \
    libglib2.0-0 \
    && rm -rf /var/lib/apt/lists/*

RUN apt-get update && apt-get install -y --no-install-recommends \
    fontconfig \
    fonts-urw-base35 \
    fonts-liberation \
    fonts-liberation2 \
    fonts-dejavu-core \
    fonts-noto-core \
 && for pkg in \
    fonts-beng fonts-beng-extra fonts-cabin fonts-cantarell fonts-dejavu-extra \
    fonts-deva fonts-deva-extra fonts-droid-fallback fonts-font-awesome \
    fonts-freefont-ttf fonts-gargi fonts-gubbi fonts-gujr fonts-gujr-extra \
    fonts-guru fonts-guru-extra fonts-indic fonts-kacst fonts-kacst-one \
    fonts-kalapi fonts-khmeros-core fonts-knda fonts-lao fonts-lato \
    fonts-lklug-sinhala fonts-lohit-beng-assamese fonts-lohit-beng-bengali \
    fonts-lohit-deva fonts-lohit-gujr fonts-lohit-guru fonts-lohit-knda \
    fonts-lohit-mlym fonts-lohit-orya fonts-lohit-taml fonts-lohit-taml-classical \
    fonts-lohit-telu fonts-mathjax fonts-mlym fonts-nakula fonts-navilu \
    fonts-noto-cjk fonts-noto-color-emoji fonts-noto-mono fonts-opendyslexic \
    fonts-open-sans fonts-opensymbol fonts-orya fonts-orya-extra fonts-pagul \
    fonts-quicksand fonts-sahadeva fonts-samyak-deva fonts-samyak-gujr \
    fonts-samyak-mlym fonts-samyak-taml fonts-sarai fonts-sil-abyssinica \
    fonts-sil-padauk fonts-smc fonts-smc-anjalioldlipi fonts-smc-chilanka \
    fonts-smc-dyuthi fonts-smc-gayathri fonts-smc-karumbi fonts-smc-keraleeyam \
    fonts-smc-manjari fonts-smc-meera fonts-smc-rachana fonts-smc-raghumalayalamsans \
    fonts-smc-suruma fonts-smc-uroob fonts-symbola fonts-taml fonts-telu \
    fonts-telu-extra fonts-teluguvijayam fonts-thai-tlwg fonts-tibetan-machine \
    fonts-tiresias fonts-tlwg-garuda fonts-tlwg-garuda-ttf fonts-tlwg-kinnari \
    fonts-tlwg-kinnari-ttf fonts-tlwg-laksaman fonts-tlwg-laksaman-ttf \
    fonts-tlwg-loma fonts-tlwg-loma-ttf fonts-tlwg-mono fonts-tlwg-mono-ttf \
    fonts-tlwg-norasi fonts-tlwg-norasi-ttf fonts-tlwg-purisa fonts-tlwg-purisa-ttf \
    fonts-tlwg-sawasdee fonts-tlwg-sawasdee-ttf fonts-tlwg-typewriter \
    fonts-tlwg-typewriter-ttf fonts-tlwg-typist fonts-tlwg-typist-ttf \
    fonts-tlwg-typo fonts-tlwg-typo-ttf fonts-tlwg-umpush fonts-tlwg-umpush-ttf \
    fonts-tlwg-waree fonts-tlwg-waree-ttf fonts-ubuntu fonts-yrsa-rasa \
    ; do \
      apt-get install -y --no-install-recommends "$pkg" \
        || echo "font package unavailable on Debian base, skipped: $pkg"; \
    done \
 && fc-cache -f \
 && rm -rf /var/lib/apt/lists/*

# The upload job shells out to `aws s3 cp` for artifacts named by s3:// URI.
RUN pip install --no-cache-dir awscli

WORKDIR /app

# The locked core dependency set, without the project itself so this layer
# caches across source changes.
COPY pyproject.toml uv.lock README.md ./
RUN uv sync --frozen --no-dev --no-install-project

# The embedding model's library is declared only in the `gpu` extra, whose
# torch is the CUDA build. This image needs the same library on a CPU torch,
# so both are installed on top of the locked set at the locked versions, with
# torch taken from the PyTorch CPU index. This is the one place the image
# departs from uv.lock, and the pins below are copied from it.
RUN uv pip install --python /app/.venv/bin/python \
      torch==2.11.0 --index-url https://download.pytorch.org/whl/cpu \
 && uv pip install --python /app/.venv/bin/python \
      sentence-transformers==5.4.1

COPY src/ ./src/
# The tree is read by an unprivileged user below; a directory copied from a
# host with owner-only permissions would be unreadable to it.
RUN chmod -R a+rX /app/src
# --inexact keeps the two packages installed above; a plain sync would remove
# anything the lock does not list.
RUN uv sync --frozen --no-dev --inexact

# Bake the embedding model's weights so a task never reaches HuggingFace at
# runtime, then pin the hub to offline mode.
ENV HF_HOME=/app/.hf
RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('BAAI/bge-large-en-v1.5')"
ENV HF_HUB_OFFLINE=1

# The app tree stays owned by root and is only read at runtime; the task's
# writable state (the data directory and the S3 cache) lives in the user's
# home, named by the environment below. Changing ownership of /app would copy
# its every file into one more layer.
RUN useradd --create-home --uid 10001 quber
USER quber
ENV QUBER_PLAYGROUND_DATA_DIR=/home/quber/data/playground \
    QUBER_S3_CACHE_DIR=/home/quber/.cache/s3

# The task-definition overrides nothing: the app is the only command.
EXPOSE 8101
CMD ["uvicorn", "quber.playground.app:app", "--host", "0.0.0.0", "--port", "8101"]
