You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The removal cannot reclaim anything. Each RUN is a layer, and a layer can only add to the image; deleting a file in a later one records a whiteout over a copy that is still shipped and still pulled.
The published :latest shows it. Compressed layer sizes from the amd64 manifest, read from the registry API:
Layer
Size
What it is
6
320.9 MB
the apt-get install + npm ci run
7
4.5 MB
COPY . /enigma-bbs
8
0.3 MB
the run that removes the toolchain
Total compressed is 401.9 MB, so layer 6 is about four fifths of the image. Layer 8, which is where the toolchain is supposedly removed, costs 0.3 MB and gives nothing back.
I cannot say how much of layer 6 is toolchain and how much is node_modules, because apt-get install and npm ci share one RUN and therefore one layer. Splitting them would at least make the two measurable separately.
A builder stage
The shape that fixes it is a multi-stage build: a build stage that keeps the toolchain, and a runtime stage that copies only what is needed.
FROM node:22-bookworm-slim AS build
RUN apt-get install -y build-essential python3 libssl-dev ...
COPY package.json package-lock.json /enigma-bbs/
RUN cd /enigma-bbs && npm ci
FROM node:22-bookworm-slim
RUN apt-get install -y lrzsz arj lhasa unrar-free p7zip-full ...
COPY --from=build /enigma-bbs/node_modules /enigma-bbs/node_modules
COPY . /enigma-bbs
Two things to get right, and the first is the reason this is not a five minute change:
node_modules carries compiled native modules -- node-pty, cpu-features, dtrace-provider. Copying them between stages is only sound because both stages share a base image and the build is native per platform rather than cross compiled, which is what fix(docker): build each architecture for real, not amd64 four times #794 established. A later move to cross compilation would break the copy, so it is worth a comment where the COPY --from lands.
The archivers stay in the runtime stage.lrzsz, arj, lhasa, unrar-free and p7zip-full are used at runtime, not at build time, and only build-essential, python3, libssl-dev, git and curl belong to the build.
Why now
It also decides a question on #800. cache-to: type=gha was set to mode=max there; mode=max exports intermediate layers from discarded stages, and with one stage there are none, so min and max cache identical content today. A builder stage is what makes max worth asking for.
I have not built the restructured image, so the saving is an inference from the layer sizes above rather than a measurement.
docker/Dockerfileinstalls a compiler toolchain to build the native modules, then removes it in a laterRUN:The removal cannot reclaim anything. Each
RUNis a layer, and a layer can only add to the image; deleting a file in a later one records a whiteout over a copy that is still shipped and still pulled.The published
:latestshows it. Compressed layer sizes from the amd64 manifest, read from the registry API:apt-get install+npm cirunCOPY . /enigma-bbsTotal compressed is 401.9 MB, so layer 6 is about four fifths of the image. Layer 8, which is where the toolchain is supposedly removed, costs 0.3 MB and gives nothing back.
I cannot say how much of layer 6 is toolchain and how much is
node_modules, becauseapt-get installandnpm cishare oneRUNand therefore one layer. Splitting them would at least make the two measurable separately.A builder stage
The shape that fixes it is a multi-stage build: a build stage that keeps the toolchain, and a runtime stage that copies only what is needed.
Two things to get right, and the first is the reason this is not a five minute change:
node_modulescarries compiled native modules -- node-pty, cpu-features, dtrace-provider. Copying them between stages is only sound because both stages share a base image and the build is native per platform rather than cross compiled, which is what fix(docker): build each architecture for real, not amd64 four times #794 established. A later move to cross compilation would break the copy, so it is worth a comment where theCOPY --fromlands.lrzsz,arj,lhasa,unrar-freeandp7zip-fullare used at runtime, not at build time, and onlybuild-essential,python3,libssl-dev,gitandcurlbelong to the build.Why now
It also decides a question on #800.
cache-to: type=ghawas set tomode=maxthere;mode=maxexports intermediate layers from discarded stages, and with one stage there are none, sominandmaxcache identical content today. A builder stage is what makesmaxworth asking for.I have not built the restructured image, so the saving is an inference from the layer sizes above rather than a measurement.