GitHub Actions docker login-action v3 三个真实坑:权限/作用域/缓存不统一

封面图

# GitHub Actions docker login-action v3 三个真实坑:权限/作用域/缓存不统一

用 GitHub Actions 跑 Docker 构建,registry 认证是最基础的操作,但 docker/login-action@v3 这个 Action 在自建 runner 环境下有三个隐蔽的坑:config.json 权限不对、认证和 Actions 缓存不同步、以及 Actions workflow 间的认证状态互相覆盖。团队踩了三次才摸清楚,这里记录完整排查过程。

问题现象

自建 runner 跑 Docker 构建时,明明已经在 workflow 里加了 docker/login-action@v3,但后续 step 仍然报 unauthorized: authentication required。日志里看不到明确错误,只是 WARNING: Could not get the repository/digest for xxx: unauthorized

三个典型场景:
1. runner 重启后第一次跑,登录失败
2. 矩阵多个 job 同时跑,只有第一个成功,后续全部 401
3. 手动在机器上 docker login 有效,但 Actions 里登录无效

坑一:config.json 权限必须是 600

根因:Docker 客户端对 ~/.docker/config.json 的权限校验比 registry 更严格。runner 机器上手动执行 docker login 时用 root 运行,写入的 config.json 权限是 600。但如果 runner 用其他用户(如 runnergithub)运行,config.json 可能继承成 644。Docker 客户端读取时会拒绝 644 的配置文件(认为存在安全风险),静默降级为未认证状态。

实测验证

# 查看当前 config.json 权限
ls -la ~/.docker/config.json
# 如果显示 -rw-r--r-- (644),Docker 会拒绝加载

# 正确权限
chmod 600 ~/.docker/config.json
# 验证 Docker 是否能读
docker --debug login ghcr.io -u username -p token
# debug 输出里找 "Auth token of xxx is valid" 表示认证成功

解法:在 runner 初始化脚本或 Ansible/Terraform provision 阶段统一设置:

mkdir -p ~/.docker
echo '{"auths":{"ghcr.io":{"auth":"base64_credentials"}}}' > ~/.docker/config.json
chmod 600 ~/.docker/config.json

验证命令

stat -c '%a' ~/.docker/config.json  # 必须返回 "600"

坑二:login-action 和机器本地 Docker 认证不同步

根因docker/login-action@v3 执行时会在 Actions 工作目录生成临时 config,不读写 runner 机器的 ~/.docker/config.json。这导致:

  • 机器上手动 docker login 登录的镜像,Actions 里的 job 看不到
  • Actions 登录过的镜像,手动 docker pull 也拉不了

Actions runner 对每个 job 生成独立的 DOCKER_CONFIG 环境变量,指向 $RUNNER_TOOLSDIRECTORY/_actions/docker/login-action@v3/... 下的临时目录。

排查命令

# 登录 action 执行后,查看它把 auth 写到了哪里
echo $DOCKER_CONFIG
# 典型输出:/home/runneradmin/actions-runner/_work/_actions/docker/login-action/v3/xxx

# 对比机器默认 config 位置
echo $HOME  # /root 或 /home/runner
ls -la ~/.docker/config.json

解法:在 workflow 里明确指定 logout 行为:

- name: Login to Container Registry
  uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}
    logout: true  # job 结束后清理,避免残留 auth 污染后续 job

如果需要在 Actions 和机器本地共享认证,最可靠的做法是用同一个 service account 的 ~/.docker/config.json,并确保 runner 进程用同一用户运行。

坑三:矩阵 job 并发时认证状态互相覆盖

根因:同一个 workflow 里多个矩阵 job 同时执行,每个 job 都会调用 docker/login-action。如果 registry 是同一家,后面的 job 登录会覆盖前面 job 的 auth token。当某个 job 先完成并执行 logout: true(或 job 结束时自动清理),后续 job 的 auth 就失效了。

典型场景:矩阵 [python3.10, python3.11, python3.12],三个 job 同时跑 build 和 push,第一个完成时清理了 auth,后两个 push 失败。

实战案例

jobs:
  build:
    strategy:
      matrix:
        python: [ '3.10', '3.11', '3.12' ]
    steps:
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
          logout: false  # 不要在矩阵 job 里用 logout=true,会炸

      - name: Build and push
        run: |
          docker build -t ghcr.io/myorg/myimage:${{ matrix.python }} .
          docker push ghcr.io/myorg/myimage:${{ matrix.python }}

更稳妥的方案:在每个矩阵 job 内部独立完成 build+push,不要把 push 放到单独的 job(那样会因为 checkout 等步骤拉长 auth 有效期,反而容易出问题)。

现在你可以做什么

第一步:检查 runner 机器的 config.json 权限

stat -c '%a' ~/.docker/config.json
# 如果不是 600,立即修复
chmod 600 ~/.docker/config.json

第二步:在 workflow 里给 login-action 加 logout: false(矩阵场景)或 logout: true(单一 job 场景),明确意图,不要默认行为

第三步:验证 Actions 和机器本地认证是否互通

# Actions workflow 里加一步 debug
  • name: Show DOCKER_CONFIG
run: echo DOCKER_CONFIG=$DOCKER_CONFIG # 机器上手动验证 docker pull ghcr.io/someorg/someimage:latest

第四步:如果用了自建 runner,在 runner 的启动服务配置里确保 HOME 环境变量一致(不要混用 root 和 runner 用户)

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