如何为BuildKit下docker build --cache-from设置缓存过期/最大时长限制
核心场景
当前CI流程使用的Docker构建命令如下:
DOCKER_BUILDKIT=1 docker build \ --cache-from registry.my.org/project-x/app:latest \ --tag registry.my.org/project-x/app:latest \ --tag registry.my.org/project-x/app:$CI_BUILD_NUM \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg BUILD_NUM=$CI_BUILD_NUM \ .
目标是限制缓存的最大存活时长,让Dockerfile第一条FROM <some-language>:latest指令约每周自动重新拉取,触发全量构建,避免基础依赖长期沿用老旧版本。
背景说明
当前使用Bitbucket Pipelines作为CI系统,该系统无法原生缓存BuildKit生成的Docker层,因此采用官方推荐的方案:构建时添加--build-arg BUILDKIT_INLINE_CACHE=1配合--cache-from参数,复用之前推送至镜像仓库的已有镜像作为构建缓存,解决大项目因CI缓存大小限制频繁出现层缓存失效的问题,实际使用效果符合预期。
日常编写的Dockerfile遵循通用分层结构,以Python项目为例(Node、PHP等其他语言项目逻辑一致):
FROM python:3.9-slim RUN pip install --upgrade pip RUN apt-get update && apt-get install --assume-yes \ gcc gettext libcurl4-openssl-dev libpangoft2-1.0-0 libssl-dev ... 其他系统依赖 WORKDIR /app COPY requirements.txt /app RUN pip install --requirement requirements.txt COPY . /app ARG BUILD_NUM RUN test -n "$BUILD_NUM" ENV RELEASE_NUM=$BUILD_NUM CMD ["python", "/app/main.py"]
分层逻辑从上到下为:
- 拉取语言运行时基础镜像
- 更新语言包管理器
- 安装极少更新的系统依赖
- 复制依赖锁定文件,安装每周更新的项目依赖
- 复制应用源码(多数场景不命中缓存,仅在monorepo单仓、修改CI配置等不涉及构建上下文的场景下可能命中)
- 写入CI递增构建号作为版本标识(从不命中缓存,执行成本极低)
原有环境的本地Docker层缓存为每周清理一次,因此镜像依赖可以保持定期更新,但切换为远端镜像作为缓存源后,前置的基础镜像、包管理器、系统库安装步骤会永久命中缓存,长期沿用老旧依赖版本,存在安全和兼容性隐患。
用周度滚动缓存击穿参数实现即可,不需要修改现有缓存逻辑,不需要额外维护镜像仓库定时任务,零侵入实现每周自动全量刷新:
- 调整CI构建脚本,在执行docker build前生成按周跳变的缓存key:
执行命令export CACHE_BUSTER_WEEK=$(date +"%G%V")即可,该值以ISO周为单位,每周一零点自动更新,同一周内所有构建拿到的值完全一致。
在原有docker build命令中新增一个构建参数传入:--build-arg CACHE_BUSTER=$CACHE_BUSTER_WEEK - 调整Dockerfile结构,在第一条FROM指令之前声明这个参数:
ARG CACHE_BUSTER=latest FROM python:3.9-slim # 原有Dockerfile内容保持不变即可
注意:上述ARG声明必须放在所有FROM指令之前,才能让基础镜像拉取步骤也随参数变化触发缓存失效,如果放在FROM之后,仅会让ARG之后的步骤失效,无法达到刷新基础镜像的目的。
实现原理
Docker构建过程中,只要某条指令的输入参数发生变化,该指令及后续所有层的缓存都会直接失效。一周内CACHE_BUSTER值固定,所有构建正常复用远端镜像缓存,不损失构建速度;每周一CACHE_BUSTER值跳变时,从拉取基础镜像开始的所有前置步骤都会自动跳过缓存,重新拉取最新的基础镜像、执行包管理器更新、安装最新系统依赖,刚好匹配每周全量刷新的需求。
额外灵活控制
如果遇到基础镜像高危安全补丁需要紧急刷新,不需要等周度自动更新,只需要临时给CACHE_BUSTER值加个自定义后缀,比如改成$CACHE_BUSTER_WEEK-hotfix,就能立刻触发全量构建,操作成本极低。
不推荐的方案:
- 定时删除镜像仓库的latest标签:需要额外维护定时任务,一旦某次全量构建失败,会直接丢失所有缓存源,导致后续所有构建都要全量执行,影响CI效率
- 构建时加
--no-cache参数:完全放弃缓存,大项目构建耗时会成倍增加- 依赖基础镜像的digest变化触发刷新:Docker默认不会主动检查远端镜像digest和本地缓存的差异,只要本地有对应tag的缓存就会直接复用,无法实现固定周期刷新的需求
内容的提问来源于stack exchange,提问作者N1ngu

