ARG         AWS_ACCOUNT=086679231553
ARG         PARENT_IMAGE_TAG=node24
ARG         BASE=${AWS_ACCOUNT}.dkr.ecr.us-east-1.amazonaws.com/docker-parent-images:${PARENT_IMAGE_TAG}
ARG         DDTRACE_ADDONS="@datadog/native-appsec @datadog/native-iast-taint-tracking @datadog/native-metrics @datadog/pprof"
# Default used by multiple stages; must be re-declared per stage because
# Docker ARGs don't persist across FROM boundaries.
ARG         HF_CACHE_SUBDIR=apps/search/.hf-cache


##### deps #####
# Workspace manifests + all dependencies for search + library packages.
# Split from dev-deps so model-cache can inherit without source files,
# preserving layer cache when only source code changes.
################
FROM        ${BASE} AS deps
WORKDIR     /var/app
ENV         CI=true
COPY        .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY        --parents apps/search/package.json packages/search-api/package.json packages/api-common/package.json packages/common/package.json packages/extensions/package.json packages/async/package.json packages/data-structures/package.json packages/search/package.json ./
USER        root
RUN         corepack enable \
 && corepack prepare --activate \
 && chown -R node:node /var/app \
 && mkdir -p /home/node/.local/share/pnpm \
 && chown -R node:node /home/node/.local
USER        node
RUN         --mount=type=secret,id=GITHUB_NPM_TOKEN,required=true,uid=1000 \
            --mount=type=cache,target=/home/node/.local/share/pnpm/store,uid=1000 \
            GITHUB_NPM_TOKEN=$(cat /run/secrets/GITHUB_NPM_TOKEN) \
            pnpm install --frozen-lockfile --ignore-scripts
# Rebuild dd-trace native addons skipped by --ignore-scripts.
ARG         DDTRACE_ADDONS
RUN         pnpm rebuild ${DDTRACE_ADDONS}


##### dev-deps #####
# Full source for search + library packages, with libraries built.
####################
FROM        deps AS dev-deps
# Root configs
COPY        --chown=node:node tsconfig.base.json tsconfig.lib.json tsconfig.app-base.json eslint.base.mjs ./
# Search source + configs
COPY        --chown=node:node apps/search/tsconfig.json apps/search/tsdown.config.ts apps/search/eslint.config.mjs ./apps/search/
COPY        --chown=node:node apps/search/src ./apps/search/src
COPY        --chown=node:node apps/search/benchmarks ./apps/search/benchmarks
COPY        --chown=node:node apps/search/scripts ./apps/search/scripts
# search-api package (proto-generated ConnectRPC types shared with server/)
COPY        --chown=node:node packages/search-api/tsconfig.json packages/search-api/tsdown.config.ts ./packages/search-api/
COPY        --chown=node:node packages/search-api/src ./packages/search-api/src
COPY        --chown=node:node packages/search-api/gen ./packages/search-api/gen
# api-common package (shared RPC utilities)
COPY        --chown=node:node packages/api-common/ ./packages/api-common/
# @coda/* packages (common depends on these)
COPY        --chown=node:node packages/async/ ./packages/async/
COPY        --chown=node:node packages/data-structures/ ./packages/data-structures/
COPY        --chown=node:node packages/search/ ./packages/search/
# Common source + configs
COPY        --chown=node:node packages/common/tsconfig.json packages/common/tsconfig.test.json packages/common/tsdown.config.ts packages/common/eslint.config.mjs ./packages/common/
COPY        --chown=node:node packages/common/src ./packages/common/src
# Extensions source + glossary data (JSON files live outside src/)
COPY        --chown=node:node packages/extensions/tsconfig.json packages/extensions/tsdown.config.ts ./packages/extensions/
COPY        --chown=node:node packages/extensions/src ./packages/extensions/src
COPY        --chown=node:node packages/extensions/graphql ./packages/extensions/graphql
COPY        --chown=node:node packages/extensions/snowflake ./packages/extensions/snowflake
COPY        --chown=node:node packages/extensions/tools ./packages/extensions/tools
# Build all library packages so dist/ is available for downstream stages.
# The directory filter auto-discovers every package under packages/.
# pnpm -r respects the dependency graph: common builds first, then dependents in parallel.
RUN         pnpm -r --filter './packages/**' build


##### model-cache #####
# Isolated model download (embedding + reranker) — only rebuilds when the
# download script, model revisions, or @huggingface/transformers version
# changes, NOT on source changes. Inherits from deps (not dev-deps) to
# avoid invalidation. Pin revisions to HuggingFace commit SHAs for
# reproducible builds.
########################
FROM        deps AS model-cache
ARG         HF_CACHE_SUBDIR=apps/search/.hf-cache
ARG         SEARCH_EMBEDDING_DTYPE=fp16
ARG         SEARCH_EMBEDDING_MODEL=mixedbread-ai/mxbai-embed-large-v1
ARG         SEARCH_EMBEDDING_REVISION=b33106f585b9ce46904ad7443a3b52b7a63e231c
ARG         SEARCH_RERANKER_DTYPE=fp16
ARG         SEARCH_RERANKER_MODEL=Xenova/bge-reranker-large
ARG         SEARCH_RERANKER_REVISION=3c4ff3c9420fb24ea62acd31e3884e09c8827f2a
# Rebuild onnxruntime-node native bindings (skipped by --ignore-scripts in deps).
# The download script loads @huggingface/transformers which may invoke ONNX
# for model validation during the download.
RUN         pnpm rebuild onnxruntime-node
COPY        apps/search/scripts/download-embedding-model.mjs ./apps/search/scripts/
RUN         --mount=type=cache,target=/var/app/apps/search/.hf-cache-store,uid=1000 \
            cp -a /var/app/apps/search/.hf-cache-store/. ./${HF_CACHE_SUBDIR} 2>/dev/null || true \
 &&         HF_CACHE_DIR=./${HF_CACHE_SUBDIR} \
            SEARCH_EMBEDDING_MODEL=${SEARCH_EMBEDDING_MODEL} \
            SEARCH_EMBEDDING_DTYPE=${SEARCH_EMBEDDING_DTYPE} \
            SEARCH_EMBEDDING_REVISION=${SEARCH_EMBEDDING_REVISION} \
            SEARCH_RERANKER_MODEL=${SEARCH_RERANKER_MODEL} \
            SEARCH_RERANKER_DTYPE=${SEARCH_RERANKER_DTYPE} \
            SEARCH_RERANKER_REVISION=${SEARCH_RERANKER_REVISION} \
            node apps/search/scripts/download-embedding-model.mjs \
 &&         cp -a ./${HF_CACHE_SUBDIR}/. /var/app/apps/search/.hf-cache-store/


##### dev #####
# Hot-reloading dev server (used by docker-compose --profile dev).
###############
FROM        dev-deps AS dev
ARG         HF_CACHE_SUBDIR=apps/search/.hf-cache
COPY        --chown=node:node --from=model-cache /var/app/${HF_CACHE_SUBDIR} ./${HF_CACHE_SUBDIR}
ENV         PORT=8081 HF_CACHE_DIR=/var/app/${HF_CACHE_SUBDIR}
EXPOSE      8081
RUN         touch apps/search/.env
CMD         ["pnpm", "--filter", "@coda/search-service", "dev"]


##### build-artifacts #####
# Compile TypeScript to ESM JavaScript via tsdown.
###########################
FROM        dev-deps AS build-artifacts
RUN         pnpm --filter @coda/search-service build


##### pnpm-deploy-search #####
# Standalone production deployment with flat node_modules.
# pnpm deploy resolves workspace deps (via their "files" field) and
# creates a self-contained directory — no symlinks, proper peer dep
# resolution for snowflake-sdk via @coda/common.
################################
FROM        build-artifacts AS pnpm-deploy-search
USER        root
RUN         mkdir -p /deploy && chown node:node /deploy
USER        node
# pnpm deploy + rebuild native addons in the flat output. onnxruntime-node
# ships prebuilt binaries — its install script runs `tsc` which isn't
# available in the prod deploy, so rebuild it without scripts.
ARG         DDTRACE_ADDONS
RUN         pnpm deploy --filter @coda/search-service --prod /deploy/search \
 && cd /deploy/search \
 && npm rebuild ${DDTRACE_ADDONS} \
 && npm rebuild --ignore-scripts onnxruntime-node


##### deploy-search #####
# Lean production image — compiled JS only, no source or dev tools.
# Single COPY from the pnpm deploy output gives a flat, self-contained layout.
# When ENABLE_CUDA=true (default), CUDA 12.x runtime libraries are installed
# for onnxruntime-node GPU inference on g4dn.xlarge (NVIDIA T4). The host GPU
# drivers are mounted by the NVIDIA Container Toolkit at runtime; only the
# userspace libraries are needed here. Set ENABLE_CUDA=false for a slim
# CPU-only image (~800 MB smaller).
#########################
FROM        ${BASE} AS deploy-search
ARG         ENABLE_CUDA=true
ARG         HF_CACHE_SUBDIR=apps/search/.hf-cache
USER        root
RUN         if [ "$ENABLE_CUDA" = "true" ]; then \
              apt-get update \
 &&           apt-get install -y --no-install-recommends gnupg curl \
 &&           curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb -o /tmp/cuda-keyring.deb \
 &&           echo "e7f219eab6fe4819cdb5c15b98233dc3420302d9c00883219cd3d896857cf48d  /tmp/cuda-keyring.deb" | sha256sum -c - \
 &&           dpkg -i /tmp/cuda-keyring.deb \
 &&           apt-get update \
 &&           apt-get install -y --no-install-recommends \
                cuda-cudart-12-8 \
                cuda-nvrtc-12-8 \
                libcublas-12-8 \
                libcudnn9-cuda-12 \
                libcufft-12-8 \
                libnvjitlink-12-8 \
                libcurand-12-8 \
 &&           dpkg -r cuda-keyring \
 &&           rm -f /etc/apt/sources.list.d/cuda-*.list \
 &&           apt-get purge -y gnupg curl \
 &&           apt-get autoremove -y \
 &&           rm -rf /var/lib/apt/lists/* /tmp/cuda-keyring.deb ; \
            fi
USER        node
WORKDIR     /var/app
COPY        --chown=node:node --from=pnpm-deploy-search /deploy/search ./
# Pre-downloaded embedding model cache (gitignored, not in pnpm deploy output).
# chown to node so the HF library can write cache metadata at runtime
# (e.g. ONNX graph optimisation files).
COPY        --chown=node:node --from=model-cache /var/app/${HF_CACHE_SUBDIR} ./.hf-cache
ENV         NODE_ENV=production PORT=8081 HF_CACHE_DIR=/var/app/.hf-cache
EXPOSE      8081
LABEL       org.opencontainers.image.source="https://github.com/theorchard/ows-coda"
HEALTHCHECK --interval=10s --timeout=5s --retries=5 --start-period=120s \
  CMD node -e "require('http').get('http://localhost:8081/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"
CMD         ["node", "--import", "dd-trace/initialize.mjs", "dist/index.mjs"]
