File size: 3,662 Bytes
39ff632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# syntax=docker/dockerfile:1
# img2threejs — production Dockerfile for a Hugging Face Docker Space.
#
# Build stages:
#   1. fixture      — regenerate the test fixture factory through the real
#                     vendored pipeline (strict gate -> explicit unreviewed
#                     hosted-preview manifest -> generation). A broken forge
#                     copy fails here.
#   2. nodesmoke    — npm ci (three + esbuild), bundle the fixture exactly as
#                     the runtime does, and execute it headlessly in node
#                     (scene-graph smoke: Group, meshes, finite bbox,
#                     sculptRuntime). A non-executing factory fails the build.
#   3. runtime      — python:3.12-slim, non-root UID 1000, tini init,
#                     uvicorn on 0.0.0.0:7860, /health HEALTHCHECK.
#                     (esbuild is a statically-linked native binary, so the
#                     runtime needs no node; node exists only in stage 2.)

# ---------------------------------------------------------------------------
FROM python:3.12-slim-bookworm AS fixture
WORKDIR /build
COPY forge/ forge/
COPY app/__init__.py app/__init__.py
COPY app/forge_bridge.py app/forge_bridge.py
COPY scripts/build_fixture_factory.py scripts/build_fixture_factory.py
COPY tests/fixtures/canned_spec.json tests/fixtures/canned_spec.json
RUN python3 scripts/build_fixture_factory.py /build/factory_fixture.ts

# ---------------------------------------------------------------------------
FROM node:22-bookworm-slim AS nodesmoke
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --no-audit --no-fund && npm cache clean --force
COPY --from=fixture /build/factory_fixture.ts /build/factory.ts
COPY app/static/viewer-core.js app/static/viewer-core.js
COPY scripts/node_smoke.mjs scripts/node_smoke.mjs
RUN set -e; \
    factory_export=$(grep -oE 'export function create[A-Za-z0-9]+Model' factory.ts | head -1 | awk '{print $3}'); \
    pascal=$(echo "$factory_export" | sed -E 's/^create//; s/Model$//'); \
    printf 'export { %s as makeModel, create%sLookDevLights as makeLights } from "./factory.ts";\nexport { mountViewer } from "%s";\n' \
      "$factory_export" "$pascal" "/build/app/static/viewer-core.js" > entry.js; \
    node_modules/.bin/esbuild entry.js \
      --bundle --format=esm --target=es2022 --minify --outfile=model.bundle.js; \
    node scripts/node_smoke.mjs /build/model.bundle.js

# ---------------------------------------------------------------------------
FROM python:3.12-slim-bookworm AS runtime

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    HOME=/home/user

RUN apt-get update \
 && apt-get install -y --no-install-recommends tini curl ca-certificates \
 && rm -rf /var/lib/apt/lists/* \
 && useradd -m -u 1000 user

USER user
WORKDIR /home/user/app

COPY --chown=user requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY --chown=user package.json package-lock.json ./
COPY --from=nodesmoke --chown=user /build/node_modules ./node_modules

# Runtime source allowlist. Build/test/deploy helpers, fixtures, local
# verification evidence and workspace state never enter the final image.
COPY --chown=user:user app/ ./app/
COPY --chown=user:user forge/ ./forge/
COPY --chown=user:user LICENSE ./LICENSE

EXPOSE 7860

# Local-docker convenience only; the HF runner probes app_port over HTTP.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
  CMD curl -fsS http://localhost:7860/health || exit 1

ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["python", "-m", "app.main"]