# ═══════════════════════════════════════════════════════════════════════════════ # ClinicalMatch AI — HuggingFace Spaces Dockerfile # Single container: Neo4j Community + FastAPI + Next.js + Nginx (supervisord) # Exposed port: 7860 (HF Spaces default) # Persistent storage: /data (Neo4j data lives here — survives restarts) # ═══════════════════════════════════════════════════════════════════════════════ # ── Stage 1: Build Next.js ──────────────────────────────────────────────────── FROM node:20-slim AS frontend-builder WORKDIR /build/frontend COPY frontend/package*.json ./ RUN npm install --legacy-peer-deps --prefer-offline COPY frontend/ ./ # Build with empty API URL so all requests are relative (Nginx routes them) ENV NEXT_PUBLIC_API_URL="" RUN npm run build # ── Stage 2: Final runtime image ────────────────────────────────────────────── FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive ENV LANG=C.UTF-8 # ── System dependencies ──────────────────────────────────────────────────────── RUN apt-get update && apt-get install -y --no-install-recommends \ python3.11 python3-pip python3.11-venv \ nginx supervisor \ curl wget ca-certificates gnupg \ && rm -rf /var/lib/apt/lists/* # ── Node.js 20 ──────────────────────────────────────────────────────────────── RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* # ── Neo4j 5.x Community + cypher-shell (official Debian repo) ───────────────── RUN curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key \ | gpg --dearmor -o /usr/share/keyrings/neo4j.gpg \ && echo "deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable 5" \ > /etc/apt/sources.list.d/neo4j.list \ && apt-get update \ && apt-get install -y neo4j cypher-shell \ && rm -rf /var/lib/apt/lists/* ENV NEO4J_HOME=/var/lib/neo4j ENV PATH="/usr/bin:${PATH}" # ── Neo4j configuration (sed replaces existing keys, avoids duplicate errors) ── RUN CFG=/etc/neo4j/neo4j.conf && \ sed -i 's|^#*\s*server\.bolt\.listen_address=.*|server.bolt.listen_address=0.0.0.0:7687|' $CFG && \ sed -i 's|^#*\s*server\.http\.listen_address=.*|server.http.listen_address=0.0.0.0:7474|' $CFG && \ sed -i 's|^#*\s*server\.directories\.data=.*|server.directories.data=/data/neo4j/data|' $CFG && \ sed -i 's|^#*\s*server\.directories\.logs=.*|server.directories.logs=/data/neo4j/logs|' $CFG && \ sed -i 's|^#*\s*server\.memory\.heap\.initial_size=.*|server.memory.heap.initial_size=256m|' $CFG && \ sed -i 's|^#*\s*server\.memory\.heap\.max_size=.*|server.memory.heap.max_size=512m|' $CFG && \ sed -i 's|^#*\s*server\.memory\.pagecache\.size=.*|server.memory.pagecache.size=128m|' $CFG && \ { \ echo "db.transaction.timeout=60s"; \ echo "dbms.logs.query.enabled=OFF"; \ echo "dbms.security.procedures.unrestricted=apoc.*"; \ echo "dbms.security.procedures.allowlist=apoc.*"; \ } >> $CFG # ── Python backend ──────────────────────────────────────────────────────────── WORKDIR /app/backend COPY backend/requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt COPY backend/ . # ── Next.js frontend (pre-built) ─────────────────────────────────────────────── WORKDIR /app/frontend # Copy only what Next.js needs to run (not dev deps) COPY --from=frontend-builder /build/frontend/.next/standalone ./ COPY --from=frontend-builder /build/frontend/.next/static ./.next/static COPY --from=frontend-builder /build/frontend/public ./public # ── Config files ─────────────────────────────────────────────────────────────── COPY docker/nginx.conf /app/docker/nginx.conf COPY docker/supervisord.conf /app/docker/supervisord.conf COPY docker/entrypoint.sh /app/docker/entrypoint.sh COPY docker/seed_on_startup.sh /app/docker/seed_on_startup.sh RUN chmod +x /app/docker/entrypoint.sh /app/docker/seed_on_startup.sh # ── Nginx writable dirs (runs without root after init) ──────────────────────── RUN mkdir -p /tmp/nginx-cache /tmp/nginx-body /tmp/nginx-run \ && chown -R www-data:www-data /var/log/nginx /var/lib/nginx 2>/dev/null || true # ── Expose & environment ─────────────────────────────────────────────────────── EXPOSE 7860 # Neo4j — local Community instance (no Aura) ENV NEO4J_URI=bolt://127.0.0.1:7687 ENV NEO4J_USERNAME=neo4j ENV NEO4J_PASSWORD=clinicalmatch2024 ENV NEO4J_DATABASE=neo4j # LLM — OpenAI-compatible (set real values via HF Spaces secrets) ENV OPENAI_API_KEY="" ENV OPENAI_BASE_URL=https://ai.aimlapi.com/v1 ENV OPENAI_MODEL=claude-opus-4-7 # Next.js standalone listens on 3000 internally; Nginx routes externally ENV PORT=3000 ENV HOSTNAME=127.0.0.1 WORKDIR /app ENTRYPOINT ["/app/docker/entrypoint.sh"]