Spaces:
Sleeping
Sleeping
File size: 6,062 Bytes
31cee57 3c31a2a dc1b199 3c31a2a dc1b199 31cee57 3c31a2a dc1b199 31cee57 3c31a2a 31cee57 732b14f 3c31a2a 31cee57 3c31a2a 31cee57 3c31a2a dc1b199 31cee57 dc1b199 3c31a2a dc1b199 31cee57 dc1b199 31cee57 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | # syntax=docker/dockerfile:1.6
# ββ Stage 1: builder βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Installs all Python dependencies (including native extensions) into a venv
# that is copied to the lean runtime image, keeping the final layer small.
FROM python:3.11-slim AS builder
# Build dependencies required by native extensions (pymupdf, faiss-cpu)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Quiet pip and prevent root-action hand-wringing in build logs.
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_ROOT_USER_ACTION=ignore \
PIP_NO_CACHE_DIR=1
WORKDIR /build
# Hatchling resolves `readme = "README.md"` and `packages = ["app"]` from
# pyproject.toml during metadata generation, so all three must be present
# *before* `pip install` runs. The build-cache key for this RUN step
# therefore changes whenever pyproject.toml, README.md, or app/ source
# changes β accepted, to keep the install step correct.
COPY pyproject.toml README.md ./
COPY app/ ./app/
# Production image installs the runtime project only β NO `[dev]` extras.
# Dev tools (pytest, mypy, black, ruff, httpx) are ~150 MB of code paths
# that production never executes; shipping them widens the attack surface
# and slows cold pulls on free hosting tiers.
#
# Non-editable install builds a wheel and drops it into site-packages, so the
# `.pth` editable link doesn't dangle when the venv is copied into stage 2.
RUN python -m venv /opt/venv && \
/opt/venv/bin/pip install --upgrade pip && \
/opt/venv/bin/pip install ".[temporal]"
# ββ Stage 2: production runtime βββββββββββββββββββββββββββββββββββββββββββββββ
FROM python:3.11-slim AS production
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Hugging Face Spaces requires the container to run as a user with UID 1000.
# Local docker / docker-compose works fine with this UID too, so we standardise
# on it here. Drop `-r` (system flag) β UID 1000 is a regular user, not a
# system account, and combining `-r` with `-u 1000` is contradictory.
RUN groupadd -g 1000 appgroup && \
useradd -u 1000 -g appgroup -d /home/user -m -s /sbin/nologin appuser
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
HOME=/home/user \
PIP_DISABLE_PIP_VERSION_CHECK=1
COPY --chown=appuser:appgroup . .
# Pre-create EVERY writable runtime path the application uses by default,
# including the legacy paths that docker-compose mounts named volumes onto
# (/mnt/data/uploads, /data, /data/faiss_index). When Docker mounts a named
# volume to a path that did NOT exist in the image, the resulting volume is
# owned by root β and the appuser (UID 1000) cannot write to it, so the very
# first POST /upload fails with PermissionError. Pre-creating the dirs as
# appuser-owned makes Docker initialise each new volume with the right
# ownership, fixing the regression.
RUN mkdir -p /home/user/.report_genius/uploads \
/home/user/.report_genius/faiss_index \
/home/user/.report_genius/cache \
/mnt/data/uploads \
/data/faiss_index \
/tmp/section_cache && \
chown -R appuser:appgroup /home/user /mnt/data /data /tmp/section_cache
USER appuser
# Defaults tuned for a public free-tier deployment. Anything here is overridable
# at runtime via the platform's secret/env-var manager.
#
# - DEV_MODE=false: production logging (no SQLAlchemy echo) and triggers the
# strict-secret startup guard in app/main.py.
# - KNOWLEDGE_BASE_ENABLED=false: source PDFs (Behrang RICS Documents,
# RAW Context) are gitignored; the seeder would only log "no files found".
# Skipping it removes a noisy startup task and the app surfaces a WARNING
# to make the trade-off visible in deploy logs.
# - DATABASE/index/upload paths land under $HOME so the container works out
# of the box on Hugging Face Spaces and on local docker without any extra
# mount config. docker-compose overrides them via env vars.
# - PORT=7860 matches the Hugging Face Spaces convention; docker-compose
# pins it to 8000 to preserve existing local URLs.
# - UVICORN_WORKERS=1 is correct for the 1-vCPU HF Spaces free tier; bigger
# hosts can override at runtime to (2 * cores) + 1.
ENV PORT=7860 \
UVICORN_WORKERS=1 \
DEV_MODE=false \
KNOWLEDGE_BASE_ENABLED=false \
DATABASE_URL=sqlite+aiosqlite:////home/user/.report_genius/dev.db \
FAISS_INDEX_PATH=/home/user/.report_genius/faiss_index \
UPLOAD_DIR=/home/user/.report_genius/uploads \
CACHE_DIR=/home/user/.report_genius/cache
# Documenting both ports β 7860 (HF Spaces default) and 8000 (docker-compose
# override). EXPOSE is informational only; it doesn't bind.
EXPOSE 7860 8000
# Image-level healthcheck so any orchestrator (HF Spaces, Kubernetes, docker
# Swarm, plain `docker run`) sees readiness without needing a separate
# compose file. start-period=45s gives the lifespan hook time to pre-warm the
# embedding model and FAISS store on cold boot.
HEALTHCHECK --interval=30s --timeout=10s --start-period=45s --retries=3 \
CMD curl -fsS "http://127.0.0.1:${PORT:-7860}/health" || exit 1
# JSON exec form with an inner `exec` so uvicorn replaces /bin/sh as PID 1.
# Without this, SIGTERM (sent by Docker / HF Spaces on every redeploy) goes
# to /bin/sh, which doesn't forward it β uvicorn never gets the chance to
# drain in-flight requests and gets SIGKILL after the 10s grace window,
# corrupting FAISS writes and dropping open generations.
CMD ["sh", "-c", "exec uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860} --workers ${UVICORN_WORKERS:-1} --loop uvloop --access-log"]
|