A private-registry token passed with ARG or ENV leaks into the image history (ENV and ARG), and a copied .npmrc stays in its layer. A secret mount shows it to one RUN step, as a file or a variable, and stores it nowhere:
# syntax=docker/dockerfile:1
FROM alpine:3
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc,required=true \
--mount=type=secret,id=npm_token,env=NPM_TOKEN \
echo "npmrc: $(wc -c < /root/.npmrc) bytes; NPM_TOKEN: ${#NPM_TOKEN} characters"
RUN ls -la /root/.npmrc 2>&1; echo "NPM_TOKEN='${NPM_TOKEN}'"printf '//registry.npmjs.org/:_authToken=npm_demo0123456789abcdef\n' > ../demo.npmrc
export NPM_TOKEN=npm_demo0123456789abcdef
docker build --no-cache --progress=plain -f ../secret.Dockerfile -t l3-secret \
--secret id=npmrc,src=../demo.npmrc --secret id=npm_token,env=NPM_TOKEN . 2>&1 |
grep -E '^#[0-9]+ [0-9.]+ '
docker history --no-trunc l3-secret | grep -c npm_demo
docker build -q --no-cache -f ../secret.Dockerfile . 2>&1 | tail -1#7 0.247 npmrc: 58 bytes; NPM_TOKEN: 24 characters #8 0.263 ls: /root/.npmrc: No such file or directory #8 0.263 NPM_TOKEN='' 0 ERROR: failed to build: failed to solve: secret npmrc: not found
The next step sees neither secret, the history holds no trace of the token, and required=true fails a build that lacks it. Without required, a missing secret leaves the path absent: BookNest's optional npmrc secret works that way, so only a developer with a private registry adds --secret id=npmrc,src=$HOME/.npmrc. A secret is not part of the cache key: a rebuild with a different token file here stayed CACHED. Similarly, --mount=type=ssh with docker build --ssh default lends a step the host's SSH agent without copying a key.