FROM python:3.11.16-slim AS base # Install uv (fast Python package manager) from official slim image. COPY --from=ghcr.io/astral-sh/uv:0.10.9 /uv /uvx /usr/local/bin/ # System libs required by WeasyPrint at runtime. RUN apt-get update && apt-get install -y --no-install-recommends \ libpango-1.0-0 \ libpangocairo-1.0-0 \ libcairo2 \ libgdk-pixbuf-2.0-0 \ libffi-dev \ shared-mime-info \ && rm -rf /var/lib/apt/lists/* # Non-root user the app runs as at runtime. -m creates a home dir: `uv run` # in the CMD below writes its cache under $HOME, and without one it has # nowhere writable to put it. RUN useradd -m -u 10001 app # Independent of $HOME regardless: keeps the cache path explicit and writable # by the app user even if the home directory assumption ever changes. ENV UV_CACHE_DIR=/tmp/uv-cache # `uv run` must use the venv built by `uv sync` below as it is: re-validating # the project would look for vendor/visionsuite (the vision-worker extra), which # this image does not carry (issue 39). `uv sync` itself is not affected by it. ENV UV_NO_SYNC=1 WORKDIR /app # Copy lockfile + project metadata first to maximize Docker layer cache. COPY pyproject.toml uv.lock ./ COPY .python-version ./ # Install ONLY backend deps from the locked dependencies. --frozen ensures # we never resolve at build time; --no-dev keeps the image lean. RUN uv sync --frozen --no-dev --extra server # Now copy the actual sources. COPY src/ ./src/ # Uploads directory (mounted as a volume in production). RUN mkdir -p /app/uploads/images /app/uploads/pdfs /app/uploads/logos /app/uploads/reports \ && chown -R app:app /app /tmp/uv-cache USER app EXPOSE 8000 # Entry point: run Alembic migrations then start Uvicorn through uv so # it uses the pinned interpreter and venv from `uv sync`. CMD ["sh", "-c", \ "uv run alembic -c src/backend/migrations/alembic.ini upgrade head && \ uv run uvicorn src.backend.main:app \ --host 0.0.0.0 --port 8000 --workers 4 \ --proxy-headers --forwarded-allow-ips='*'"]