askmydocs / Dockerfile
Prabu17's picture
Upload folder using huggingface_hub
26532e2 verified
Raw History Blame Contribute Delete
6.77 kB
# AskMyDocs — all-in-one Hugging Face Docker Space.
#
# Everything the app needs runs inside this single container:
# PostgreSQL 16 + Qdrant + Redis + FastAPI (uvicorn) + Celery worker
# + a Caddy reverse proxy in front of the API.
#
# This file is copied to the root of the Space repository by
# scripts/hf_space_stage.py, so all COPY paths are relative to the repo root.
# The Space README.md must set `app_port: 7860`. Caddy listens on 7860 and
# proxies to uvicorn on 8000. This mirrors the production topology in
# docker-compose.prod.yml without Docker-in-Docker.
#
# HF Spaces run the container as UID 1000, so every step that writes owned
# files happens as that user, and no bind-mount tricks are used.
# ---------------------------------------------------------------------------
# Frontend build stage.
# The Next.js app is built here, then only the compiled output + the Node
# runtime are copied into the final image. Its /api/* handlers are dynamic
# (server-rendered on demand), so a static export is not possible: the UI runs
# as a real Node server and proxies to the API on 127.0.0.1:8000 (the default
# in frontend/src/lib), which keeps everything same-origin and avoids CORS.
# ---------------------------------------------------------------------------
FROM node:20-slim AS frontend-builder
WORKDIR /fe
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ ./
# Build, then drop devDependencies so only the production tree is copied into
# the final image (`next` itself is a runtime dependency).
RUN npm run build && npm prune --omit=dev
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
DEBIAN_FRONTEND=noninteractive \
# Sentence-transformers / HF Hub weight cache. Warmed at build time.
HF_HOME=/home/user/hf_home \
HF_HUB_OFFLINE=0 \
# Embedded service paths (internal only; only 7860 is exposed).
PGDATA=/home/user/pgdata \
QDRANT__STORAGE__STORAGE_PATH=/home/user/qdrant_storage \
HOME=/home/user \
PATH=/home/user/.local/bin:$PATH
# ---------------------------------------------------------------------------
# System packages: PostgreSQL 16, Redis, Caddy, and Qdrant runtime deps.
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y --no-install-recommends \
postgresql \
redis-server \
caddy \
curl \
ca-certificates \
build-essential \
libgomp1 \
zstd \
&& rm -rf /var/lib/apt/lists/*
# Qdrant ships self-contained musl binaries (no glibc dependency), which is
# far lighter than installing its .deb (which drags in all of Caddy).
ARG QDRANT_VERSION=v1.13.0
RUN curl -fsSL \
"https://github.com/qdrant/qdrant/releases/download/${QDRANT_VERSION}/qdrant-x86_64-unknown-linux-musl.tar.gz" \
-o /tmp/qdrant.tgz \
&& mkdir -p /opt/qdrant \
&& tar -xzf /tmp/qdrant.tgz -C /opt/qdrant \
&& rm /tmp/qdrant.tgz
# ---------------------------------------------------------------------------
# Python dependencies (same set as the production Dockerfile).
# ---------------------------------------------------------------------------
WORKDIR /home/user/app
COPY pyproject.toml README.md ./
# torch MUST come from the CPU-only index. The default PyPI linux wheel pulls
# the whole CUDA stack (nvidia-cublas, cudnn, nccl, ...) — several GB a CPU-only
# Space can never use. This is what OOMKilled (exit 137) the free cpu-basic
# build: floating `torch>=2.2` kept resolving to newer, larger wheels over time
# even with no repo changes.
#
# torch is installed FIRST, into the real site-packages (no --prefix trick), so
# the project install below sees it already satisfying `torch>=2.2` and does
# not re-resolve it against PyPI.
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir \
--index-url https://download.pytorch.org/whl/cpu \
--extra-index-url https://pypi.org/simple \
"torch>=2.2"
RUN pip install --no-cache-dir .
# Application code + runtime scripts.
COPY app ./app
COPY scripts ./scripts
# Node runtime for the frontend server. Copied explicitly rather than merging
# all of /usr/local, which would clobber this image's Python installation.
COPY --from=node:20-slim /usr/local/bin/node /usr/local/bin/node
COPY --from=node:20-slim /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
# Frontend source + prebuilt .next output + the pruned runtime deps.
# node_modules is required at runtime: `next start` is invoked from
# ./node_modules/.bin/next by start.sh.
COPY frontend ./frontend
COPY --from=frontend-builder /fe/.next ./frontend/.next
COPY --from=frontend-builder /fe/node_modules ./frontend/node_modules
# Space entrypoint helpers.
COPY start.sh ./start.sh
COPY warmup_models.py ./warmup_models.py
RUN chmod +x ./start.sh
# Corpus: baked into the image so the Space is useful on a cold start (HF
# container disks are wiped on every restart). data/corpus is always tracked
# in git; data/uploads_seed is optional (see deploy/huggingface/README.md).
COPY data/corpus ./data/corpus
COPY data/uploads_seed/ ./data/uploads_seed/
# ---------------------------------------------------------------------------
# Build-time warm-up: bake BGE-M3 + the cross-encoder weights into the image.
# Both repos are ungated (no license acceptance needed). HF_TOKEN is an optional
# *build secret* for rate-limit relief; a failure here still leaves the image
# usable (the model then downloads on first startup instead).
# ---------------------------------------------------------------------------
RUN --mount=type=secret,id=HF_TOKEN,required=false \
HF_TOKEN="$(cat /run/secrets/HF_TOKEN 2>/dev/null || true)" \
python warmup_models.py || echo "[warn] model warm-up skipped"
# ---------------------------------------------------------------------------
# Runtime user (HF Spaces use UID 1000) and writable directories.
# ---------------------------------------------------------------------------
RUN useradd -m -u 1000 user \
&& mkdir -p /home/user/pgdata /home/user/pgsocket /home/user/qdrant_storage /home/user/app/data/uploads \
&& chown -R user:user /home/user
USER user
EXPOSE 7860
# start.sh starts Postgres + Redis + Qdrant, initializes the schema, ingests
# the corpus on first boot, then supervises uvicorn/Celery/Caddy.
CMD ["./start.sh"]