Spaces:
Running
Running
| # Multi-stage Dockerfile for Hugging Face Spaces | |
| # Copies pre-built services from GHCR images into single runtime | |
| # Build arg for image tag (defaults to latest, overridden by deploy workflow with SHA) | |
| ARG IMAGE_TAG=main-sha-45a463a | |
| # Build arg for git commit hash (passed from GitHub Actions) | |
| ARG GIT_COMMIT=45a463a691d190bfc41275335703fbc29195abb5 | |
| # Stage 1: Extract Next.js standalone build | |
| FROM ghcr.io/glutamatt/neural-runner/neural-runner-app:${IMAGE_TAG} AS nextjs-build | |
| # Contains /app/.next/standalone, /app/.next/static, /app/public | |
| # Stage 2: Extract Garmin MCP server | |
| FROM ghcr.io/glutamatt/neural-runner/neural-runner-garmin-mcp:${IMAGE_TAG} AS garmin-mcp-build | |
| # Contains /app/garmin_mcp Python package | |
| # Stage 3: Extract COROS MCP server | |
| FROM ghcr.io/glutamatt/neural-runner/neural-runner-coros-mcp:${IMAGE_TAG} AS coros-mcp-build | |
| # Contains /app/coros_mcp Python package | |
| # Stage 4: Extract Agent Tools server | |
| FROM ghcr.io/glutamatt/neural-runner/neural-runner-agent-tools:${IMAGE_TAG} AS agent-tools-build | |
| # Contains /app/agent_tools Python package | |
| # Stage 5: Extract HF token sync sidecar | |
| FROM ghcr.io/glutamatt/neural-runner/neural-runner-hf-storage-sync:${IMAGE_TAG} AS hf-storage-sync-build | |
| # Contains /app/hf-storage-sync.py + huggingface_hub, watchfiles installed | |
| # Stage 6: Tailscale binaries (VPN to garmin-connector on homelab) | |
| FROM tailscale/tailscale:latest AS tailscale | |
| # Final stage: Runtime environment for HF Spaces | |
| FROM python:3.12-slim | |
| # Re-declare build arg in final stage (ARGs don't carry over across FROM stages) | |
| ARG GIT_COMMIT=45a463a691d190bfc41275335703fbc29195abb5 | |
| # Install Node.js 20.x and process-compose | |
| RUN apt-get update && apt-get install -y \ | |
| curl \ | |
| wget \ | |
| jq \ | |
| bubblewrap \ | |
| fonts-dejavu-core fonts-liberation fonts-noto-color-emoji \ | |
| && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ | |
| && apt-get install -y nodejs \ | |
| && apt-get clean \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Tailscale binaries (userspace networking — no TUN device on HF Space) | |
| COPY --from=tailscale /usr/local/bin/tailscaled /usr/local/bin/ | |
| COPY --from=tailscale /usr/local/bin/tailscale /usr/local/bin/ | |
| # Install process-compose (modern supervisor alternative) | |
| RUN wget -qO- https://github.com/F1bonacc1/process-compose/releases/latest/download/process-compose_linux_amd64.tar.gz | \ | |
| tar xz -C /usr/local/bin process-compose && \ | |
| chmod +x /usr/local/bin/process-compose | |
| # Set working directory | |
| WORKDIR /home/user/app | |
| # Create user with UID 1000 (HF requirement) | |
| RUN useradd -m -u 1000 user && \ | |
| chown -R user:user /home/user | |
| # Copy Next.js standalone build from stage 1 | |
| # The app target has files at /app/frontend/ (WORKDIR in app target) | |
| COPY --from=nextjs-build --chown=user:user /app/frontend ./frontend | |
| # Copy MCP server source code and dependencies to separate directories | |
| COPY --from=garmin-mcp-build --chown=user:user /app ./mcp-servers/garmin-mcp | |
| COPY --from=coros-mcp-build --chown=user:user /app ./mcp-servers/coros-mcp | |
| COPY --from=agent-tools-build --chown=user:user /app ./mcp-servers/agent-tools | |
| # Install MCP servers (regular install, NOT editable to avoid .pth path issues in multi-stage builds) | |
| # IMPORTANT: Install forked garminconnect FIRST from the local copy inside the garmin-mcp image, | |
| # otherwise pip resolves 'garminconnect' from PyPI (which lacks schedule_workout, delete_workout, etc.) | |
| RUN pip install --no-cache-dir ./mcp-servers/garmin-mcp/python-garminconnect && \ | |
| pip install --no-cache-dir ./mcp-servers/garmin-mcp && \ | |
| pip install --no-cache-dir ./mcp-servers/coros-mcp && \ | |
| pip install --no-cache-dir ./mcp-servers/agent-tools && \ | |
| pip install --no-cache-dir PySocks | |
| # Install hf-storage-sync deps and copy script | |
| RUN pip install --no-cache-dir huggingface_hub watchfiles | |
| COPY --from=hf-storage-sync-build --chown=user:user /app/hf-storage-sync.py ./hf-storage-sync.py | |
| # Copy process-compose configuration | |
| COPY --chown=user:user process-compose.yaml ./ | |
| # Copy boot script | |
| COPY --chown=user:user boot.py ./ | |
| # Set environment variables | |
| ENV PORT=7860 \ | |
| NODE_ENV=production \ | |
| MCP_GARMIN_URL=http://localhost:8080/mcp \ | |
| MCP_COROS_URL=http://localhost:8081/mcp \ | |
| MCP_AGENT_URL=http://localhost:8082/mcp \ | |
| PYTHONUNBUFFERED=1 \ | |
| NODE_OPTIONS="--max-old-space-size=4096" \ | |
| GIT_COMMIT=${GIT_COMMIT} | |
| # Switch to user | |
| USER user | |
| # Expose HF Spaces default port | |
| EXPOSE 7860 | |
| # Start services via boot script | |
| CMD ["python3", "boot.py"] | |