
问题:私有镜像构建每次都从零开始
自建 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 私有镜像拉取失败时,按这个顺序排查:
- 检查
~/.docker/config.json权限是600:ls -la ~/.docker/config.json - 确认 login-action 加了
persist: true参数 - 如果用了
docker/build-push-action,检查它的registry参数是否和 login-action 一致 - 加一步
docker pull验证,pull 成功说明认证链路通了
这个坑的概率不高,但一旦遇到,错误日志只会说 unauthorized,不会告诉你是因为 config.json 权限 644。记住这个细节,下次 5 分钟内解决。
更多交流点击入群






