build(docker): ship only the runtime assets, without node or dev packages

The image ran npm install without --omit=dev, so every published tag
carried Cypress with its downloaded binary, ESLint and the whole dev
tree next to nodejs and npm. The npm install layer alone was 229 MB of
the 349 MB compressed 2.0.0 image, and all seven npm audit findings
(extract-zip, form-data, tmp, qs, uuid via @cypress/request) came from
it, although the app only serves five vendored asset directories.

A node:22-slim stage now runs npm install --omit=dev, whose postinstall
writes static/vendor, and the final runtime stage copies just that
directory onto the Python base. A dev stage keeps nodejs and npm for
docker-compose, which bind-mounts app/ and runs npm install on start;
compose now builds that target. .dockerignore keeps a local
app/node_modules and app/static/vendor out of COPY app/.

Measured on make build: the runtime image is 142 MB uncompressed, has
no node, npm, node_modules or Cypress cache, and serves /, /de/ and
every vendored asset with 200. The dev stage has node 20.19.2 and npm
9.2.0. make test passes, hadolint included, with 107 Cypress tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-10 17:16:23 +02:00
parent f830153cf4
commit 793e2de899
3 changed files with 20 additions and 5 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
app/node_modules/
app/static/vendor/

View File

@@ -1,12 +1,16 @@
FROM python:3.12-slim
FROM node:22-slim AS assets
WORKDIR /app
COPY app/package.json ./
COPY app/scripts ./scripts
RUN npm install --omit=dev --no-audit --no-fund
FROM python:3.12-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
FLASK_HOST=0.0.0.0
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm && rm -rf /var/lib/apt/lists/*
WORKDIR /tmp/build
COPY pyproject.toml README.md main.py ./
@@ -15,6 +19,14 @@ RUN python -m pip install --no-cache-dir .
WORKDIR /app
COPY app/ .
RUN npm install --prefix /app
CMD ["python", "app.py"]
FROM base AS dev
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm && rm -rf /var/lib/apt/lists/*
FROM base AS runtime
COPY --from=assets /app/static/vendor ./static/vendor

View File

@@ -7,6 +7,7 @@ services:
build:
context: .
dockerfile: Dockerfile
target: dev
container_name: portfolio
ports:
- "${PORT:?PORT must be set in .env (see env.example)}:${PORT:?PORT must be set in .env (see env.example)}"