Matrix-Fossil-Agent / Dockerfile
hmgill's picture
Upload 29 files
85f0898 verified
Raw
History Blame Contribute Delete
4.39 kB
# 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"]