FROM python:3.11-slim # System deps for Playwright + Node.js + git RUN apt-get update && apt-get install -y \ wget curl gnupg ca-certificates git \ build-essential python3-dev \ libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \ libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \ libgbm1 libasound2 libxshmfence1 libx11-xcb1 \ libxcb-dri3-0 libpango-1.0-0 libcairo2 libfontconfig1 \ fonts-liberation libappindicator3-1 xdg-utils \ && rm -rf /var/lib/apt/lists/* # Install Node.js 20 LTS (needed to run ever-jobs NestJS API) RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* # Create non-root user (HF Spaces requirement) RUN useradd -m -u 1000 user USER user ENV HOME=/home/user PATH=/home/user/.local/bin:$PATH WORKDIR /app # Install Python deps (separate layer — cached unless requirements.txt changes) COPY --chown=user requirements.txt . RUN pip install --no-cache-dir --upgrade pip \ && pip install --no-cache-dir -r requirements.txt # Install Playwright Chromium browser RUN playwright install chromium # Install Scrapling's stealth browser (Camoufox) + fetcher deps for the # anti-block fetch layer. Non-fatal: if the Camoufox download hiccups, the # lightweight HTTP TLS-impersonation path (Fetcher) and the requests fallback # still work — only the Cloudflare-bypass StealthyFetcher would be unavailable. RUN (scrapling install || python -m scrapling install || true) # Install Tectonic (self-contained LaTeX engine) for compiling user resume # LaTeX → PDF. Pin a specific MUSL-STATIC release: the rolling drop-sh installer # shipped a glibc build that crashed at COMPILE time with "free(): invalid # pointer" (a glibc heap abort — note it ran `tectonic --version` fine, so a # version gate did not catch it). The musl static binary does not link glibc, so # it cannot hit that class of crash and is maximally portable in the container. ENV TECTONIC_CACHE_DIR=/home/user/.cache/tectonic RUN mkdir -p /home/user/.local/bin /home/user/.cache/tectonic \ && cd /tmp \ && curl --proto '=https' --tlsv1.2 -fsSL -o tectonic.tar.gz \ https://github.com/tectonic-typesetting/tectonic/releases/download/tectonic%400.16.9/tectonic-0.16.9-x86_64-unknown-linux-musl.tar.gz \ && tar -xzf tectonic.tar.gz \ && mv tectonic /home/user/.local/bin/tectonic \ && chmod +x /home/user/.local/bin/tectonic \ && rm -f tectonic.tar.gz \ && tectonic --version # Pre-warm the bundle cache by compiling the SANITIZED resume — the exact source # the server compiles at runtime (_sanitize_for_tectonic strips fontawesome5's OTF # loader, which SEGFAULTS Tectonic's XeTeX, and the FiraMono font loader). This # caches every package/font the real compile uses AND proves the sanitized template # actually compiles at BUILD time (grep the build log for "TECTONIC_REAL_WARMUP"). # Build gate is intentionally non-fatal so a transient warmup hiccup never bricks # the Space; the OK/FAILED marker + log tail make the outcome visible. COPY --chown=user assets/default_resume.tectonic.tex /tmp/warm-resume.tex RUN (tectonic --outdir /tmp /tmp/warm-resume.tex \ && echo "TECTONIC_REAL_WARMUP_OK" \ || (echo "TECTONIC_REAL_WARMUP_FAILED — full log follows:" \ && cat /tmp/warm-resume.log 2>/dev/null | tail -80 || true)) \ && rm -f /tmp/warm-resume.pdf # Hard build gate: fail the image build if the Tectonic binary is missing/broken # (so we never silently ship a no-LaTeX-engine image — R16). RUN tectonic --version # Clone ever-jobs and install Node deps (separate layer — cached unless repo changes) # vendor/ is in .dockerignore so it never comes from the build context # nest-cli.json forces webpack which isn't installed; ts-node (nest start) is used at runtime instead RUN git clone https://github.com/ever-jobs/ever-jobs.git vendor/ever-jobs --depth=1 \ && cd vendor/ever-jobs \ && npm install --legacy-peer-deps \ && sed -i 's/"webpack": true/"webpack": false/g' nest-cli.json # Copy app code (this layer re-runs on every code change, which is expected) COPY --chown=user . . # Create required runtime directories RUN mkdir -p data/resume data/output/resumes data/output/reports \ data/research_cache data/logs data/output/run_history # Make startup script executable RUN chmod +x start.sh # Expose Streamlit port EXPOSE 7860 # Only keep non-conflicting Streamlit env vars. # STREAMLIT_SERVER_PORT and STREAMLIT_SERVER_ADDRESS are intentionally omitted: # they would conflict with the proxy architecture where api_server.py runs # uvicorn on 7860 and Streamlit is started on 127.0.0.1:8501 via CLI args. ENV STREAMLIT_SERVER_HEADLESS=true \ STREAMLIT_BROWSER_GATHER_USAGE_STATS=false CMD ["./start.sh"]