.dockerignore 看起来只是“少传点文件”,但它同时决定了镜像里有什么、COPY 层的缓存能不能命中。它的匹配规则又不是 .gitignore 的那一套:`*.log` 只管根目录。这篇用一个故意塞满各种文件的目录,逐条验证规则的效果。
退出码 0,用时 1625 ms,transferring context(COPY 一步)31.47MB 镜像里 /app 有 11 个文件:./Dockerfile ./README.md ./build.log ./build/out.bin ./main.txt ./node_modules/big.bin ./notes.md ./src/a.txt ./src/b.log ./src/build/inner.txt ./tmp/x/y/temp
没有 .dockerignore,COPY . /app 把 30 MiB 的 node_modules、日志、构建产物全带进镜像,上下文传输 31.47 MB,构建 1625 ms。
.dockerignore 的内容:node_modules | *.log | /build | **/temp | *.md | !README.md 退出码 0,用时 285 ms,transferring context(COPY 一步)509B 镜像里 /app 有 7 个文件:./.dockerignore ./Dockerfile ./README.md ./main.txt ./src/a.txt ./src/b.log ./src/build/inner.txt 被忽略的文件:./build.log ./build/out.bin ./node_modules/big.bin ./notes.md ./tmp/x/y/temp 说明:`*.log` 只匹配根目录这一层的 build.log,src/b.log 没被忽略;`/build` 只匹配根目录的 build,src/build/inner.txt 没被忽略;`**/temp` 匹配任意深度;`*.md` 之后的 `!README.md` 把 README.md 加回来(后面的规则覆盖前面的)
写了 6 行规则后传输降到 509 B、285 ms:node_modules 与 **/temp 把目录整个忽略;*.md 之后的 !README.md 把 README 加回来(后面的规则覆盖前面的)。陷阱是 *.log 和 /build 只匹配根目录那一层,src/b.log、src/build/inner.txt 都还在镜像里。
忽略规则 **/*.log 之后镜像里还有的 log 文件:(没有)
**/*.log 才能忽略所有层级的 log。这和 .gitignore 的直觉不同,是最容易漏的地方。
[不忽略 build.log] 只改了 build.log 后再构建:COPY 层 重新执行,用时 250 ms [忽略 build.log] 只改了 build.log 后再构建:COPY 层 命中缓存(CACHED),用时 235 ms
只改了 build.log:不忽略它时 COPY . /app 层要重新执行,忽略它时这一层命中缓存。把日志、构建输出、临时文件加进 .dockerignore,能让依赖 COPY . 的后续层不被无关变化打断。
只有 .dockerignore(写了 Dockerfile、.dockerignore、b.txt):退出码 0;镜像里 /app 有 ['./a.txt'] 再加一个 Dockerfile.dockerignore(只写了 a.txt):退出码 0;镜像里 /app 有 ['./.dockerignore', './Dockerfile', './Dockerfile.dockerignore', './b.txt'](它取代了 .dockerignore,不是叠加)
把 Dockerfile 和 .dockerignore 写进规则,COPY . 不会把它们带进镜像(它们仍然会被发给构建器)。每个 Dockerfile 可以有自己的 Dockerfile.dockerignore,它存在时完全取代 .dockerignore,实验里原来的规则一条都没生效。