# TailwindCSS built in its own stage, from the official Node image, instead # of `curl | bash` against nodesource inside the final image (point 2, C2). FROM node:20.20.2-bookworm-slim AS tailwind-build WORKDIR /app/flask_app COPY src/frontend/flask_app/ ./ # Versione esatta, non `tailwindcss@3`. Con il vincolo aperto ogni ricostruzione # dell'immagine poteva prendere una minor diversa e produrre un CSS diverso senza # che nessuno l'avesse validato: per un sistema che produce evidenze per audit # ISO 9001 / IATF 16949 è lo stesso problema che il punto 12 risolve per le # librerie del browser. Aggiornarla è una decisione, non un effetto collaterale. RUN npm install tailwindcss@3.4.19 && \ npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --minify FROM python:3.11.16-slim AS base # uv from the official slim image (fast Python package manager). COPY --from=ghcr.io/astral-sh/uv:0.10.9 /uv /uvx /usr/local/bin/ # 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 # 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, then overlay the TailwindCSS build produced in # the node stage above — the final image never installs Node. COPY src/frontend/flask_app/ ./flask_app/ COPY --from=tailwind-build /app/flask_app/static/css/tailwind.css ./flask_app/static/css/tailwind.css # Compile Flask-Babel translation catalogs. WORKDIR /app/flask_app RUN uv run --project /app pybabel compile -d translations && \ chown -R app:app /app /tmp/uv-cache # Commit mostrato nel piede di pagina (#70). .git e' escluso dal contesto di # build (.dockerignore), quindi lo passa chi costruisce; facoltativo, senza il # piede mostra solo la versione. Uso: # APP_COMMIT=$(git rev-parse --short HEAD) docker compose build client ARG APP_COMMIT="" ENV APP_COMMIT=${APP_COMMIT} USER app 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()"]