Docker build 时缓存命中改了的代码没打进镜像
上周改了个 Python 服务的 bug,本地 docker build -t myapp . 重新打镜像,再 docker run 起来验证,结果跑出来的行为跟改之前一模一样——那个被修掉的报错还在。第一反应是改漏了文件,来回对比了好几遍,确认代码确实改了,镜像的构建时间也是新的,可运行的就是旧逻辑。这种"改动凭空消失"的感觉最让人抓狂,下面记录我是怎么定位的。
问题现象
本地改完源码,执行构建,构建日志一路走完没有报错,docker images 里镜像的 CREATED 时间也确实是刚才。但把这个镜像跑成容器后,业务表现和改动前完全一致,像是这次修改根本没进镜像。反复改、反复构建,现象稳定复现,像是改动被构建过程"吃"掉了。
排查过程
先看构建日志,发现 COPY 那一步有点不对劲:
Step 5/7 : COPY . /app
---> Using cache
---> 9a3c1f2b8e7d
我明明刚改了文件,COPY 却显示命中缓存。直觉上"内容变了缓存就该失效",于是怀疑是不是构建上下文没带上新文件。Docker 构建时是把当前目录打成 tar 传给 daemon 的,.dockerignore 会决定哪些文件进上下文。我翻出项目根目录的 .dockerignore,里面赫然写着:
__pycache__
*.pyc
.git
venv
src/legacy
而这次我改的,恰恰就是 src/legacy/ 下面的一个模块。也就是说,改动文件在构建开始之前就被 .dockerignore 排除出了上下文,Docker 拿到的"要复制的文件集合"根本没变,COPY 层的内容哈希自然也没变,缓存直接复用,旧代码原封不动留在镜像里。为了验证,我把那个文件临时移出被忽略目录再构建,镜像立刻就带上了新逻辑,实锤。
根因
Docker 的层缓存对 COPY/ADD 是按"被复制文件的内容校验和"来判定的。而"被复制的文件集合"由两件事决定:构建上下文(当前目录)和 .dockerignore 的排除规则。只要某次改动的文件落在 .dockerignore 的排除范围内,它就进不了上下文,COPY 层看到的输入没变化,于是跳过重建、复用旧缓存层——改动永远不会进镜像。这个问题在 CI 里更隐蔽,因为很多人为了加快构建,会往 .dockerignore 里一股脑加排除项,时间久了连自己都忘了里面有业务目录。
解决办法
- 改完先强制全量验证一次:
docker build --no-cache -t myapp .,绕过所有缓存,确认改动确实生效了,再正常构建。 - 核对
.dockerignore:别把业务代码目录误伤进去;排除项越少越安全,实在要排除就写精确,不要拿宽泛的目录名去堵。 - 用
docker history <镜像>看每层:确认 COPY 那一层是不是真的重做了(没有<missing>或者Using cache标记),而不是靠"构建时间新"来自我安慰。 - CI 慎用
--cache-from:从远端拉基础镜像复用缓存时,要确保你的源码层每次都能因内容变化而失效,否则一样会吃到旧代码。
小结
"改了代码镜像却没变"这类问题,第一嫌疑人永远是 .dockerignore + 构建上下文,而不是 Docker 缓存本身有 bug。COPY 缓存只看"进入上下文的文件内容哈希",被 ignore 的改动等于不存在。下次遇到,先 cat .dockerignore 再看缓存,能省下大把瞎猜的时间。
- 点赞
- 收藏
- 关注作者
评论(0)