docker/login-action@v3 私有镜像认证失败:config.json 权限与 persistent 模式详解

docker login-action v3 私有镜像认证失败

问题:私有镜像构建每次都从零开始

自建 runner 团队(5台机器)部署后,发现一个诡异现象:每次触发 workflow,明明镜像层缓存应该命中,Docker 构建却总是从第一条指令重新执行。日志里没有任何报错,只是最后拉取镜像时一行 warning:

WARNING! Your password will be stored unencrypted in /home runner/.docker/config.json

警告说完,缓存失效就这么静悄悄发生了。

错误日志:Unauthorized 与 login-action 的 silent fail

完整报错出现在 GitHub Actions 的 Build and push Docker image step:

Error response from daemon: unauthorized: authentication required
Step 1/6 : FROM registry.example.com/private/python:3.11-slim

这个错误不是每次都出现——免费 runner(GitHub 托管)基本正常,自建 runner 高概率触发。问题的根因藏在 docker/login-action@v3 的行为差异里。

根因:两套认证系统的优先级冲突

自建 runner 上跑 Docker build,通常依赖两套认证机制:

  • 机器本地的 Docker config~/.docker/config.json,手动 docker login 的凭证存在这里
  • login-action 的虚拟临时文件:action 在 $RUNNER_TEMP 里生成了一个独立的 config.json,workflow 结束后自动清理

关键在于:BuildKit 默认优先读临时 config,不是本地那个。如果 login-action 因为某种原因没有正确写入临时文件(常见原因:runner 缺少写权限、CARGO_HOME 等环境变量冲突),临时 config 是空的,BuildKit 就拿不到认证信息,直接向 registry 发请求,得到 401 Unauthorized。

实战案例:config.json 权限必须是 600

最隐蔽的坑:runner 机器的 ~/.docker/config.json 权限必须是 600,不是 644

自建 runner 第一次部署时,我用 root 初始化 Docker,手动 docker login 写入了 config.json。文件权限默认是 644

ls -la ~/.docker/config.json
# -rw-r--r-- 1 root root 1234 Jun  3 10:00 /root/.docker/config.json

runner 以 runner 用户运行,读取 /root/.docker/config.json 时权限不足,Docker 静默降级到匿名访问。login-action 检测到 registry 需要认证,但读不到本地凭证,报错 unauthorized

解法:三步走

第一步:统一 config.json 位置

确认 runner 以哪个用户运行,就在那个用户的 home 目录下执行 login,不要混用 root:

sudo -u runner docker login registry.example.com
# 验证
sudo -u runner cat ~runner/.docker/config.json

第二步:修复权限为 600

chmod 600 ~runner/.docker/config.json
ls -la ~runner/.docker/config.json
# -rw------- 1 runner runner 1234 Jun  3 10:00 /home/runner/.docker/config.json

第三步:workflow 里用 login-action 并指定 persistent 模式

- name: Login to private registry
  uses: docker/login-action@v3
  with:
    registry: registry.example.com
    username: ${{ secrets.REGISTRY_USER }}
    password: ${{ secrets.REGISTRY_PASS }}
    # 关键:让 action 写持久化 config,不是临时文件
    persist: true

persist: true 让 login-action 写入 ~/.docker/config.json(runner 用户的 home),而不是 $RUNNER_TEMP 临时目录。这样 BuildKit 在任何时候都能读到认证信息。

验证命令

修复后,在 workflow 里加一步确认认证状态:

- name: Verify Docker auth
  run: |
    docker pull registry.example.com/private/python:3.11-slim
    echo "✅ 认证成功,镜像拉取正常"

如果 persist: true 设置正确,这个 step 会直接通过,不需要额外的 debug 命令。

现在你可以做什么

遇到自建 runner 上 Docker 私有镜像拉取失败时,按这个顺序排查:

  1. 检查 ~/.docker/config.json 权限是 600ls -la ~/.docker/config.json
  2. 确认 login-action 加了 persist: true 参数
  3. 如果用了 docker/build-push-action,检查它的 registry 参数是否和 login-action 一致
  4. 加一步 docker pull 验证,pull 成功说明认证链路通了

这个坑的概率不高,但一旦遇到,错误日志只会说 unauthorized,不会告诉你是因为 config.json 权限 644。记住这个细节,下次 5 分钟内解决。

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