Spaces:
Running
Running
Download apps/api/Dockerfile from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 6.9 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/Dockerfile
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/api/Dockerfile
-
curl -L -o Dockerfile https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/Dockerfile
6.9 kB
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # GuestFlow API - Production Dockerfile | |
| # Multi-stage build for optimized production image | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # βββ Stage 1: Pruner βββββββββββββββββββββββββββββββββββββββ | |
| FROM node:20-slim AS pruner | |
| RUN apt-get update && apt-get install -y --no-install-recommends libc6-dev sqlite3 libsqlite3-dev g++ make python3 && rm -rf /var/lib/apt/lists/* | |
| WORKDIR /app | |
| RUN npm install -g turbo@2.8.12 | |
| COPY . . | |
| RUN turbo prune --scope=@micprotocol/api --docker | |
| # βββ Stage 2: Dependencies βββββββββββββββββββββββββββββββββ | |
| FROM node:20-slim AS deps | |
| RUN apt-get update && apt-get install -y --no-install-recommends libc6-dev sqlite3 libsqlite3-dev g++ make python3 openssl && rm -rf /var/lib/apt/lists/* | |
| WORKDIR /app | |
| # Copy pruned package.json and lockfile | |
| COPY --from=pruner /app/out/json/ . | |
| # Install pnpm | |
| RUN npm install -g pnpm@10.30.3 | |
| # Install dependencies | |
| RUN pnpm install --frozen-lockfile | |
| # βββ Stage 3: Builder ββββββββββββββββββββββββββββββββββββββ | |
| FROM node:20-slim AS builder | |
| RUN apt-get update && apt-get install -y --no-install-recommends libc6-dev sqlite3 libsqlite3-dev g++ make python3 openssl && rm -rf /var/lib/apt/lists/* | |
| WORKDIR /app | |
| # Install pnpm | |
| RUN npm install -g pnpm@10.30.3 | |
| # Copy dependencies from deps stage (includes all node_modules, lockfile, and package.json files) | |
| COPY --from=deps /app /app | |
| # Copy pruned source files on top of it | |
| COPY --from=pruner /app/out/full/ . | |
| # Generate Prisma client | |
| RUN cd apps/api && npx prisma generate | |
| # Build application | |
| RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm build --filter @micprotocol/api | |
| # Prune devDependencies to keep only production dependencies | |
| RUN CI=true pnpm install --prod --frozen-lockfile | |
| # βββ Stage 4: Production βββββββββββββββββββββββββββββββββ | |
| FROM node:20-slim AS runner | |
| WORKDIR /app | |
| ENV NODE_ENV=production | |
| ENV PORT=4000 | |
| ENV MEILISEARCH_URL=http://localhost:7700 | |
| ENV MEILISEARCH_API_KEY=masterKey | |
| RUN apt-get update && apt-get install -y --no-install-recommends libc6-dev openssl libgcc1 tini ffmpeg sqlite3 libsqlite3-dev netcat-openbsd python3 && rm -rf /var/lib/apt/lists/* | |
| # Copy Meilisearch binary from the official image | |
| COPY --from=getmeili/meilisearch:v1.9.0 /bin/meilisearch /usr/local/bin/meilisearch | |
| # Create non-root user | |
| RUN addgroup --system --gid 1001 nodejs | |
| RUN adduser --system --uid 1001 nestjs | |
| # Copy entire pruned application and dependencies | |
| COPY --from=builder --chown=nestjs:nodejs /app /app | |
| # βββ Python clip-scoring engine ββββββββββββββββββββββββββ | |
| # The BullMQ clip-scoring processor spawns | |
| # `python3 services/clip-scorer/pipeline.py` via execFile | |
| # (apps/api/src/queue/clip-scoring.processor.ts), so this image must | |
| # ship python3, the ML stack, and the pipeline source. torch is pinned | |
| # to the CPU wheel index: CUDA is unnecessary for the 224px ViT-B/32 | |
| # inference and would add ~2GB of NVIDIA runtime deps. | |
| # The venv is pinned to the system python: without it uv probes the | |
| # newest CPython it can download (3.15 here), for which torchvision has | |
| # no wheels β the clip-scoring stack then can't resolve. torch AND | |
| # torchvision must land in one resolution pass from the CPU index: | |
| # open-clip-torch declares both as deps, and a PyPI-resolved torchvision | |
| # breaks open_clip import with "operator torchvision::nms does not | |
| # exist" (same failure class as the standalone clip-scorer image). | |
| # Build with the monorepo root as context (docker-compose does exactly | |
| # that for the `api` and `worker` services) so services/clip-scorer is | |
| # in scope β turbo prune does not carry it (not a pnpm workspace). | |
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv | |
| COPY --chown=nestjs:nodejs services/clip-scorer/ /app/services/clip-scorer/ | |
| ENV PATH="/opt/venv/bin:$PATH" \ | |
| HF_HOME=/app/.hf-cache | |
| RUN uv venv --python /usr/bin/python3 /opt/venv \ | |
| && VIRTUAL_ENV=/opt/venv uv pip install --index-url https://download.pytorch.org/whl/cpu "torch>=2.0.0" "torchvision>=0.15.0" \ | |
| && VIRTUAL_ENV=/opt/venv uv pip install "open-clip-torch>=2.20.0" "transformers>=4.30.0" "Pillow>=10.0.0" "soundfile>=0.12.0" "pyloudnorm>=0.1.0" "numpy>=1.24.0" | |
| # Pre-baked CLIP weights come from the standalone clip-scorer image | |
| # (its build already downloaded the model) so this build does NOT need | |
| # Hub access. Placed after the venv layer so upgrading torch does not | |
| # force a weight re-copy (and vice versa). | |
| COPY --from=localhost/micprotocol-clip-scorer:audit-verify \ | |
| /root/.cache/huggingface/hub/ \ | |
| /app/.hf-cache/hub/ | |
| # Offline verification: fail the build if the baked cache ever stops | |
| # matching what open_clip 3.x expects to find. BART (transcript NLI) is | |
| # intentionally not baked β it is a ~1.6GB download and the pipeline | |
| # falls back to the heuristic scorer when it cannot download at runtime. | |
| RUN HF_HUB_OFFLINE=1 python3 -c "import open_clip; open_clip.create_model_and_transforms('ViT-B-32', pretrained='laion2b_s34b_b79k')" | |
| # Create data directory for Meilisearch and set ownership | |
| RUN mkdir -p /app/meili_data && chown -R nestjs:nodejs /app | |
| # Switch to non-root user | |
| USER nestjs | |
| # Create startup script | |
| RUN echo '#!/bin/sh' > /app/start.sh && \ | |
| echo 'MEILI_HOST=$(echo ${MEILISEARCH_HOST:-localhost} | sed "s|http://||;s|https://||")' >> /app/start.sh && \ | |
| echo 'MEILI_PORT=$(echo $MEILI_HOST | cut -d: -f2)' >> /app/start.sh && \ | |
| echo '[ -z "$MEILI_PORT" ] && MEILI_PORT=7700' >> /app/start.sh && \ | |
| echo 'MEILI_HOST=$(echo $MEILI_HOST | cut -d: -f1)' >> /app/start.sh && \ | |
| echo 'echo "Waiting for Meilisearch at $MEILI_HOST:$MEILI_PORT..."' >> /app/start.sh && \ | |
| echo 'while ! nc -z $MEILI_HOST $MEILI_PORT; do' >> /app/start.sh && \ | |
| echo ' sleep 0.5' >> /app/start.sh && \ | |
| echo 'done' >> /app/start.sh && \ | |
| echo 'echo "Meilisearch is ready!"' >> /app/start.sh && \ | |
| echo 'node /app/apps/api/dist/main.js' >> /app/start.sh && \ | |
| chmod +x /app/start.sh | |
| # Expose ports | |
| EXPOSE 4000 | |
| EXPOSE 7700 | |
| # Health check | |
| HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ | |
| CMD node -e "require('http').get('http://localhost:4000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))" | |
| # Start application | |
| ENTRYPOINT ["tini", "--"] | |
| CMD ["/app/start.sh"] | |