# Fossil Agent — ONE Space, GPU. Everything in-process: SAM3, the agent, Chainlit. # # ============================================================================ # WHY THE BUILD USED TO TIME OUT # ============================================================================ # # The old Dockerfile started from python:3.11-slim and ran `pip install torch`: # # * pip's resolver could not settle this dependency graph. MEASURED, on the exact # requirements below: pip did not finish in 280s (I killed it); uv did it in # under 1s. chainlit + google-adk + litellm + transformers have >1,600 published # versions between them and pip backtracks through them. THIS was the timeout — # the resolver, before a single byte of torch was downloaded. # # * ...and then it downloaded 3.1 GB anyway: torch (527 MB) plus the four separate # nvidia-* wheels it declares (2,589 MB). # # Fixed by: a base image that already HAS torch+CUDA (a cached layer pull, not a pip # download), uv instead of pip, and no `accelerate` — it hard-requires torch and was # dragging all 3.1 GB back in through the side door. Plain `transformers` does NOT # require torch; every torch entry in its metadata is an `extra`. # # ============================================================================ # THE ROOT/USER SPLIT — the thing that just bit us # ============================================================================ # # pytorch/pytorch keeps its Python in a ROOT-OWNED conda env at /opt/conda. HF Spaces # want the app to RUN as uid 1000. Those are two different phases, and collapsing them # is what produced: # # USER user # RUN uv pip install --system ... # -> error: failed to remove .../jinja2-3.1.4.dist-info/INSTALLER: Permission denied # # uv has to overwrite packages conda already put there (jinja2, filelock, sympy...), # and a non-root user cannot. So: install as root, then drop to `user` for runtime # only. The container still runs unprivileged, which is the part that matters. # ============================================================================ # Different CUDA? Pick another tag: https://hub.docker.com/r/pytorch/pytorch/tags FROM pytorch/pytorch:2.5.1-cuda12.1-cudnn9-runtime # --------------------------------------------------------------------------- # BUILD PHASE — as root. # --------------------------------------------------------------------------- # opencv-python-headless dlopens these at import even though it draws nothing. RUN apt-get update && apt-get install -y --no-install-recommends \ libgl1 libglib2.0-0 && \ rm -rf /var/lib/apt/lists/* WORKDIR /app # Dependencies before source, so this layer caches across code-only pushes. COPY requirements.txt . # uv, not pip. See above: this is the fix, not an optimisation. # --system installs into the image's conda Python — which is where torch already is. RUN pip install --no-cache-dir uv && \ uv pip install --system --no-cache -r requirements.txt COPY . . # --------------------------------------------------------------------------- # RUNTIME PHASE — unprivileged. # --------------------------------------------------------------------------- # HF Spaces run the container as uid 1000. Everything the app writes at runtime — # artefacts, uploads, the HF weight cache — must be writable by that user. RUN useradd -m -u 1000 user && \ mkdir -p /app/output /home/user/work /home/user/.cache/huggingface && \ chown -R user:user /app /home/user USER user ENV HOME=/home/user \ PYTHONUNBUFFERED=1 \ FOSSIL_WORK_DIR=/home/user/work \ HF_HOME=/home/user/.cache/huggingface \ FOSSIL_BG_BACKEND=hf \ FOSSIL_BG_STRICT=1 # SAM3's weights (~3 GB) are fetched on FIRST REQUEST, not at build time — so a cold # Space's first segmentation takes minutes and every one after takes seconds. Don't # mistake a cold start for a hang. With persistent storage attached, set HF_HOME=/data # so they survive a restart instead of re-downloading on every cold boot. # # FOSSIL_BG_STRICT=1: RMBG-2.0 only. Without it, a bad HF_TOKEN silently falls back to # GrabCut — a worse but *plausible* matte, so the run still looks fine and the masks # are merely a bit off. Nobody notices until a mesh has been built on top of them. EXPOSE 7860 CMD ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860", "--headless"]