# syntax=docker/dockerfile:1.7 # # automAIta — single-container image for HuggingFace Spaces. # # HF Space contract (https://huggingface.co/docs/hub/spaces-sdks-docker): # - Container runs as UID 1000. → user `app`, USER app below. # - Single port exposed (default 7860). → EXPOSE + uvicorn --port 7860. # - $HOME must be writable by the runtime user. → ENV HOME=/home/app. # - Persistent storage (when enabled in Space settings) is mounted at /data # at RUNTIME ONLY — not available during build. Pre-create + chown the dir # so the runtime mount lands on an owned, writable path. # - Don't use `VOLUME` for /data (HF doesn't honor it). # - Build-time secrets must be declared with --mount=type=secret,id=NAME and # read from /run/secrets/NAME — DO NOT bake secrets into env vars. # ---------- Stage 1: build the React PWA ---------- FROM node:20-alpine AS frontend-build WORKDIR /fe COPY frontend/package.json frontend/package-lock.json* ./ RUN npm install --no-audit --no-fund COPY frontend/ ./ RUN npm run build # ---------- Stage 2: runtime (FastAPI serves API + built static) ---------- FROM python:3.12-slim AS runtime ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ UV_LINK_MODE=copy \ UV_COMPILE_BYTECODE=1 \ UV_PROJECT_ENVIRONMENT=/app/.venv \ HOME=/home/app \ PATH="/app/.venv/bin:/home/app/.local/bin:${PATH}" \ AUTOMAITA_STATIC_DIR=/app/static \ AUTOMAITA_DATA_DIR=/data # Non-root user matching the HF Space UID. RUN groupadd --system --gid 1000 app \ && useradd --system --uid 1000 --gid 1000 --create-home --home-dir /home/app --shell /bin/bash app WORKDIR /app # Install uv (pip is fine for a single binary install). RUN pip install --no-cache-dir uv==0.5.11 # Install backend deps. Cached layer if pyproject + lock are unchanged. COPY --chown=app:app backend/pyproject.toml backend/uv.lock* ./ RUN uv sync --frozen --no-dev || uv sync --no-dev # Backend source. COPY --chown=app:app backend/app ./app # Built frontend → /app/static. COPY --from=frontend-build --chown=app:app /fe/dist ./static # Pre-create /data so the runtime persistent-storage mount lands on a path # already owned by uid 1000. If no storage is attached, this directory still # exists and is writable — the app falls back to ephemeral writes. RUN mkdir -p /data && chown -R 1000:1000 /data RUN chown -R app:app /app /home/app USER app EXPOSE 7860 CMD ["uv", "run", "--no-sync", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]