File size: 4,390 Bytes
719c98b
bcde336
bef7996
85f0898
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876cc3e
719c98b
bef7996
 
719c98b
 
 
 
bef7996
876cc3e
719c98b
876cc3e
 
719c98b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876cc3e
bcde336
876cc3e
 
bcde336
719c98b
bcde336
719c98b
876cc3e
719c98b
 
 
 
 
 
 
 
876cc3e
 
85f0898
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
# 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"]