
# 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 用其他用户(如 runner 或 github)运行,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 用户)
更多交流点击入群






