September 24, 2026 路 #8
The token baked into the layer
This image needs a private package during the build. What will still be true after you push it?
FROM node:22-bookworm-slim
WORKDIR /app
ARG NPM_TOKEN
ENV NPM_TOKEN=$NPM_TOKEN
COPY package.json package-lock.json ./
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc \
&& npm ci \
&& rm .npmrc
COPY . .
CMD ["node", "server.js"]Next puzzle in UTC
One try. The first answer sticks.
How to fix it
Deleting .npmrc only changes a later layer. ENV persists in the config, and the RUN line that expanded the token is still in history. Use a BuildKit secret mount.
rm .npmrc feels like cleanup. Image layers are additive. The RUN that wrote the token is still in the history, and anyone with the image can read it with docker history or by unpacking layers. ENV NPM_TOKEN is worse: it stays in the image config and is present in every container started from the image, including production.
ARG alone is still recoverable from history on many builds. It is not a secret store.
Fix
BuildKit can mount a secret for a single command without writing it into a layer.
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
&& echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc \
&& npm ci \
&& rm .npmrc
Build with docker build --secret id=npm_token,env=NPM_TOKEN. At runtime, inject the app's own credentials from the orchestrator, a file mount, or a secret manager. Do not copy them into ENV.