GitHub Actions Docker 构建缓存失效的两个真实原因:token过期和dockerignore

封面图

问题:CI 构建每次从零编译

用 GitHub Actions 跑 Docker 构建,明明什么都没改,却每次都从零开始。缓存命中率 0%,CI 时间从 2 分钟飙升到 12 分钟。

根因有两个,都很隐蔽:GITHUB_TOKEN 24h 过期导致 cache-from 认证静默失败,.dockerignore 漏写导致 BuildKit 上下文哈希每次变化。两个同时存在时,缓存完全失效。

坑1:GITHUB_TOKEN 24h 过期,cache-from 认证失败

GitHub Actions 的 GITHUB_TOKEN 有效期只有 24 小时。用 cache-from: type=registry,ref=ghcr.io/owner/image 引用私有镜像时,BuildKit 需要在构建时拉取缓存层。token 过期后 registry 认证失败,cache-from 静默失效——不报错,只是跳过缓存直接构建。

免费套餐的 GITHUB_TOKEN 过期时间是 24h(付费是 60 天,但 workflow 每次触发都会刷新)。夜里跑没问题,第二天早上再看又是全量编译。

- name: Check GITHUB_TOKEN and registry auth
  run: |
    # 验证 token 是否能访问 ghcr.io
    echo "Token prefix: ${{ secrets.GITHUB_TOKEN:0:4 }}..."
    TOKEN=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
      https://ghcr.io/token/service=container_registry&scope=repository:${{ github.repository }}:pull)
    if echo "$TOKEN" | jq -e '.token' > /dev/null 2>&1; then
      echo "Registry auth: OK"
    else
      echo "Registry auth: FAILED - cache-from will silently skip"
    fi

坑2:.dockerignore 漏写导致上下文哈希变化

BuildKit 的缓存 Key 包含"构建上下文"的哈希值。如果 .dockerignore 漏掉了大目录(.venv、node_modules、.git),每次上传的上下文大小差异巨大,BuildKit 认为上下文变了,缓存失效。

实测:一个 Next.js 项目漏掉 node_modules,上下文从 12MB 变成 420MB,构建时间从 1 分钟变成 11 分钟。

# 包含式写法:以 * 开头显式排除所有,再逐个恢复需要的目录
*
!src/
!package.json
!package-lock.json
!requirements.txt
!.dockerignore

# 排除大目录(显式列出)
.venv/
node_modules/
__pycache__/
.git/
.idea/
.vscode/
dist/
build/

完整修复方案

两个坑一起修:用 docker/login-action@v3 续命 token,用包含式 .dockerignore 稳定上下文哈希。

name: Build and Push Docker Image

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # 修复坑1:登录续命 GITHUB_TOKEN
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 修复坑2:确认 .dockerignore
      - name: Verify dockerignore
        run: |
          echo "=== .dockerignore contents ==="
          cat .dockerignore
          echo "=== context size ==="
          du -sh .

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
          cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:latest
          cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:latest,mode=max

效果验证

修复前后对比:

# 修复前(两个坑同时存在)
context size: 420MB
cache hit rate: 0%
build time: 11m 32s

# 修复后
context size: 12MB
cache hit rate: 73%
build time: 1m 18s

login-action 让 cache-from 认证恢复,命中率从 0% 回到 70%+。dockerignore 优化后上下文从 420MB 降到 12MB,BuildKit 缓存 Key 稳定,后续构建跳过所有未更改的层。

现在你可以做什么

第一步:检查你的 .dockerignore 是否有包含式写法(以 * 开头),没有的话改成上面格式,重新推一次触发新构建。

第二步:在 workflow 里加 docker/login-action@v3,放在 build-push-action 之前,确保每次构建前 token 有效。

第三步:看 CI 日志里是否出现 Using cacheCached (pull) 字样,出现则缓存生效。

第四步:记住 GITHUB_TOKEN 免费套餐 24h 过期、付费 60 天。如果 workflow 间隔超过这个时间,cache-from 会静默失效,需要在 workflow 里加定时触发保持 token 活跃。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享