Rebuilding Only What Changed

Because each stage copies only what it needs, an edit reaches only the stages that read the changed file. Edit a test, then the front end, and list the steps each rebuild actually ran:

Which steps rerun after an editCSS
ran() { echo "$1:"; docker build --progress=plain "${@:2}" . 2>&1 |
  grep -B1 -E '^#[0-9]+ DONE' | grep -oE '\[(runtime|test) [0-9]/[0-9]\].*' ||
  echo "(all cached)"; }
echo "// test idea $(date +%s)" >> test/api.test.js
ran "runtime, test edited" -t l3-booknest-api:1.1
ran "test, test edited" --target test -t l3-booknest-api:1.1-test
echo "<!-- banner $(date +%s) -->" >> public/index.html
ran "runtime, front end edited" -t l3-booknest-api:1.1
git checkout -q test/api.test.js public/index.html
Output
runtime, test edited:
(all cached)
test, test edited:
[test 2/2] COPY . .
runtime, front end edited:
[runtime 6/6] COPY public ./public

No runtime step reads test/, so the test edit left the runtime image entirely cached, and the front-end edit reran one step. With a single COPY . . in runtime, every edit anywhere would rebuild it. Two options refine this: --no-cache-filter deps reruns one stage (with an empty cache mount) and keeps the rest cached, and COPY --link makes a copied layer independent of the layers beneath it, so a new base image does not force a re-copy.