
问题:明明没改代码,缓存却每次都失效
你在 GitHub Actions 里配了 Docker BuildKit 的 type=registry 缓存,本地跑好好的,Actions 里却每次都从零编译。查看日志,cache source is untrusted 或者直接没有 CACHED 标记,但也没有任何报错信息。这不是你的配置有问题,而是 GitHub 平台的机制导致的。
根因:GITHUB_TOKEN 有效期只有 24 小时
GitHub Actions 的 GITHUB_TOKEN 默认有效期是 24 小时。当使用 type=registry 缓存时,BuildKit 会把缓存镜像 push 到你的 registry(如 GHCR、阿里云、腾讯云),push 时用的是 GITHUB_TOKEN 生成的短期凭证。
24 小时后这个凭证过期,cache-from 引用的是一个需要重新认证的镜像。BuildKit 的处理策略是:认证失败时 静默跳过缓存,而不是报错中断构建。结果就是每次都走全新构建,缓存形同虚设。
# 本地正常,因为 token 不过期
$ docker buildx build --push \
--cache-from type=registry,ref=ghcr.io/yourname/cache:latest \
--cache-to type=registry,ref=ghcr.io/yourname/cache:latest \
.
# GitHub Actions 里,24 小时后缓存静默失效
# 日志里只有 "CACHED" 消失,没有任何错误提示
# 42s → 11m32s(每次都重新编译)
解法一:改用 type=gha(最简单,推荐)
GitHub Actions 原生支持 type=gha 缓存,缓存存在 GitHub 的 Actions cache 服务里,不依赖外部 registry 凭证,也没有 24 小时过期问题。
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v3
- name: Build with GitHub Actions cache
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/yourname/image:latest
cache-from: type=gha
cache-to: type=gha,mode=max
mode=max 会缓存所有层,适合构建时间长的项目。如果想省空间,用 mode=min 只缓存最终镜像层。
解法二:OIDC federation 消除 token 过期(适合企业内部 registry)
如果必须用外部 registry(阿里云、腾讯云、ECR 等),可以通过 OIDC federation 让 GitHub Actions 使用永不过期的凭证来 push 缓存镜像。
核心思路:在云厂商那边创建一个 OIDC provider,绑定 GitHub repo 的 trust policy,之后 Actions 里用 aws oidc-provider 或云厂商的 GitHub OIDC action 获取长期凭证。
# 阿里云 RAM 的 trust policy 示例(限制只有指定 repo 才能操作)
{
"Version": "2015-05-01",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "acs:ram::123456789:oidc-provider/github.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc:aud": "https://github.com/yourname/yourrepo"
}
}
}]
}
获取凭证后,正常 docker login 即可,此时 token 不会过期,type=registry 缓存正常工作。
解法三:加 docker/login-action 续命(治标)
如果暂时无法改架构,可以加 docker/login-action@v3 在每次构建前重新认证。这个方法能撑一段时间,但本质没有解决 token 过期问题——只是让每次构建都能重新拿到有效凭证,然后写入缓存。
steps:
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
cache-from: type=registry,ref=ghcr.io/yourname/cache:latest
cache-to: type=registry,ref=ghcr.io/yourname/cache:latest,mode=max
但注意:如果 registry 是 GHCR,GITHUB_TOKEN 本身就是 ghcr.io 的有效凭证,所以 docker/login-action 实际上可以解决 GHCR 的 token 过期问题。真正麻烦的是阿里云/腾讯云/ECR 这种外部 registry——它们的 token 和 GitHub 的不互通。
三种方案对比
| 方案 | 适用场景 | 配置复杂度 | 是否有失效风险 |
|---|---|---|---|
| type=gha | 镜像在 GHCR / 公开 registry | 低(改一行配置) | 无 |
| OIDC federation | 企业内部 registry(阿里云/腾讯云/ECR) | 高(需要云厂商配置) | 无 |
| login-action 续命 | 临时修复 / 外部 registry | 中 | 有(每次 push 重新认证,耗时长) |
实战:如何验证缓存是否生效
加了缓存配置后,最直接验证方式:看第二次构建的时间。如果第二次还是 10 分钟+,说明缓存根本没命中。
# 在 workflow 里加一行输出
- name: Build time
run: echo "Build completed at $(date)"
更精确的方式是看 buildx 的输出有没有 CACHED 标记:
# 第一次构建(无缓存)
# => [8/8] RUN pip install -r requirements.txt
# 123.4s
# 第二次构建(有缓存)
# => [8/8] CACHED [7/8] RUN pip install -r requirements.txt
# 0.3s
如果看到 CACHED 消失,说明缓存失效了,回到本文排查是哪一步出了问题。
现在你可以做什么
第一步:检查你现在的 workflow 配置是 type=registry 还是 type=gha,如果用 GHCR 且配了 type=registry,改成 type=gha 只需改两行,缓存命中率立即从 0% 恢复到 80%+。
第二步:如果用的是阿里云/腾讯云 registry,先加 docker/login-action@v3 看能否解决;如果持续出现缓存失效,再考虑走 OIDC federation 路线(需要云厂商侧配置)。
第三步:在 Actions 日志里搜索 CACHED 关键字,确认缓存是否真的命中。没有 CACHED 标记就说明缓存没生效,不要只看构建时间——有时候构建时间短是因为代码没变化,而不是缓存生效。
更多交流点击入群






