FROM python:3.11-slim AS base # uv from the official slim image (fast Python package manager). COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ # Node.js 20 is needed at build time to compile TailwindCSS. RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # Resolve Python deps from the project lockfile (only the `client` extra). COPY pyproject.toml uv.lock ./ COPY .python-version ./ RUN uv sync --frozen --no-dev --extra client # Copy the Flask app sources. COPY src/frontend/flask_app/ ./flask_app/ # Build TailwindCSS (one-shot; no watcher in production image). WORKDIR /app/flask_app RUN npm install tailwindcss@3 && \ npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --minify # Compile Flask-Babel translation catalogs. RUN uv run --project /app pybabel compile -d translations EXPOSE 5000 # Gunicorn behind Nginx/Traefik. Worker count and proxy trust kept in sync # with the per-tablet rate-limiting fix (see V2.0.0 perf commit). CMD ["uv", "run", "--project", "/app", "gunicorn", \ "--workers", "5", \ "--threads", "4", \ "--worker-class", "gthread", \ "--timeout", "60", \ "--bind", "0.0.0.0:5000", \ "--access-logfile", "-", \ "--forwarded-allow-ips", "*", \ "app:create_app()"]