# app/Dockerfile # Build context is the je-validation project root, NOT app/ — compose sets # `context: ..`, and the HF Space repo mirrors the same layout with this file # copied to its root. The context's .dockerignore keeps raw snapshots out. # Build stage: python base with uv on top; produces /app/.venv only. FROM python:3.12-slim AS builder COPY --from=ghcr.io/astral-sh/uv:0.5 /uv /usr/local/bin/uv ENV UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy WORKDIR /app COPY app/pyproject.toml app/uv.lock ./ RUN uv sync --frozen --no-dev --no-install-project COPY app/src ./src RUN uv sync --frozen --no-dev # Runtime stage: plain python, no uv. The venv is copied to the same /app path # the builder created it at, so its interpreter links and the editable install # stay valid. FROM python:3.12-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PATH="/app/.venv/bin:$PATH" \ JE_DATA_DIR=/data RUN groupadd --gid 1000 appuser \ && useradd --uid 1000 --gid 1000 --create-home appuser WORKDIR /app COPY --from=builder --chown=appuser:appuser /app/.venv ./.venv COPY --chown=appuser:appuser app/src ./src # Spec + DS-A snapshot + recorded run history baked in so the image is # self-contained (HF Spaces has no volumes; its disk resets on restart, so # baked runs are the only ones that survive). Local compose shadows all of # these with its ../docs and ../data mounts. The deploy script stages a # curated subset of data/runs/console; local builds bake whatever is there. COPY --chown=appuser:appuser docs/spec ./docs/spec COPY --chown=appuser:appuser data/snapshots/ds_a /data/snapshots/ds_a COPY --chown=appuser:appuser data/runs/console /data/runs/console RUN chown -R appuser:appuser /data USER appuser EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=3)" CMD ["uvicorn", "je_validation.console.api:app", \ "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]