# ═══════════════════════════════════════════════════════════════════════════════ # 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 \ # Java for Neo4j openjdk-17-jre-headless \ # Python python3.11 python3-pip python3.11-venv \ # Web / infra nginx \ supervisor \ # Utilities 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 Community 2026.04.0 ───────────────────────────────────────────────── ENV NEO4J_VERSION=2026.04.0 ENV NEO4J_HOME=/opt/neo4j ENV PATH="${NEO4J_HOME}/bin:${PATH}" RUN wget -q "https://dist.neo4j.org/neo4j-community-${NEO4J_VERSION}-unix.tar.gz" \ && tar -xzf "neo4j-community-${NEO4J_VERSION}-unix.tar.gz" -C /opt \ && mv "/opt/neo4j-community-${NEO4J_VERSION}" /opt/neo4j \ && rm "neo4j-community-${NEO4J_VERSION}-unix.tar.gz" \ && rm -rf /opt/neo4j/data # will be symlinked to /data at runtime # Neo4j configuration — listen on all interfaces, use /data for persistence RUN { \ echo "server.bolt.listen_address=0.0.0.0:7687"; \ echo "server.http.listen_address=0.0.0.0:7474"; \ echo "server.directories.data=/data/neo4j/data"; \ echo "server.directories.logs=/data/neo4j/logs"; \ echo "server.directories.plugins=/opt/neo4j/plugins"; \ echo "dbms.security.auth_enabled=true"; \ echo "server.memory.heap.initial_size=512m"; \ echo "server.memory.heap.max_size=1g"; \ echo "server.memory.pagecache.size=256m"; \ echo "db.transaction.timeout=60s"; \ echo "dbms.logs.query.enabled=OFF"; \ } >> /opt/neo4j/conf/neo4j.conf # ── 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 # ── Neo4j seed dump ──────────────────────────────────────────────────────────── COPY docker/neo4j.dump /app/docker/neo4j.dump # ── 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 RUN chmod +x /app/docker/entrypoint.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"]