Files
homepage.veen.world/Dockerfile
Kevin Veen-Birkenbach 793e2de899 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>
2026-09-10 17:17:05 +02:00

33 lines
654 B
Docker

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
WORKDIR /tmp/build
COPY pyproject.toml README.md main.py ./
COPY app ./app
RUN python -m pip install --no-cache-dir .
WORKDIR /app
COPY 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