# syntax=docker/dockerfile:1.2 # ---- deps stage: needs git + a credentialed clone of the private ARF repos # (agentic_reliability_framework, ARF-Bayesian-Pricing-Calculator). # This stage is discarded after build -- the credential never reaches # the final image's layers, env, or git config. ---- # # GH_PAT is read via a BuildKit secret mount, not `ARG` -- an ARG's value is # printed in plaintext as part of the logged RUN command that uses it (this # is exactly how a real, live token ended up visible in a Render deploy log # this session). A secret mount's value is never written to a log line or # an image layer. REQUIRES a matching setup step in Render's dashboard # before this will build: Render's Docker service settings -> Secret Files # -> add a file named exactly `gh_pat` containing the token value (nothing # else in the file). The old `GH_PAT` environment variable is no longer # read by this Dockerfile and can be removed once this is confirmed working. FROM python:3.12-slim AS deps RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* RUN --mount=type=secret,id=gh_pat,dst=/etc/secrets/gh_pat \ git config --global url."https://$(cat /etc/secrets/gh_pat)@github.com/".insteadOf "https://github.com/" RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" WORKDIR /app COPY requirements.txt . # torch has no explicit pin anywhere in this dependency tree -- it's pulled in # transitively by sentence-transformers (for agentic_reliability_framework's # RAG/semantic-memory features) and, left to the default PyPI index, resolves # to the CUDA-enabled build (nvidia-cusparselt, cuda-toolkit, nvidia-nccl, ...) # even though this service runs on CPU-only Render instances. That variant's # extra weight is a real contributor to out-of-memory deploy failures. # # A separate `pip install torch==... --index-url .../cpu` RUN before this one # does NOT work: it's a distinct resolve that only knows about the CPU wheel; # the very next `pip install -r requirements.txt`, seeing no --index-url, only # has the default PyPI index in view and re-resolves torch from there, # silently replacing the CPU build with the CUDA one at the same version # number (confirmed happening in a real deploy -- final `pip install` log # showed plain `torch-2.13.0` plus the full nvidia/cuda-toolkit/triton stack, # not `torch-2.13.0+cpu`). Putting torch and -r requirements.txt in one # `pip install` call, with the CPU wheelhouse as the primary --index-url and # PyPI as --extra-index-url, makes it a single resolve: torch is satisfied # from the CPU index and nothing later re-derives a different build for it. # Version pinned to 2.13.0 to match exactly what pip's resolver already chose # for this dependency tree (confirmed available on the CPU index for # cp312/manylinux before pinning it here, not assumed). RUN pip install --no-cache-dir \ --index-url https://download.pytorch.org/whl/cpu \ --extra-index-url https://pypi.org/simple \ torch==2.13.0 \ -r requirements.txt # ---- final stage: just the built venv + app code, no git, no credential ---- FROM python:3.12-slim COPY --from=deps /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" WORKDIR /app COPY . . # Shell form (not exec/JSON-array form) deliberately -- ${PORT:-7860} only # expands with a real shell interpreting the command; exec form passes # arguments literally with no variable substitution at all. Render injects # PORT and expects the app to bind to it (its deploy log explicitly failed # port-scanning for it: "Bind your service to at least one port"); the # Hugging Face Space mirror sets no such variable and expects the # conventional default, 7860. One image, correct on both targets. CMD uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860}