先拆小命令定位
把命令拆小,不直接跑整条 CI。
| 场景 | 命令位置 | 第一刀 |
|---|---|---|
| 本地登录慢 | Mac 终端 | 查 DNS、TLS、Docker Desktop |
| Actions 登录失败 | workflow | 查 permissions 和 token |
| push 卡住 | CI 发布镜像 | 查镜像层和缓存 |
| 服务器 pull 失败 | 部署机 | 查 read 权限和出口 |
如果日志停在 Login Succeeded 之后,那就不是登录超时,而是 push 或 pull 慢。这个区分能省很多无效换 token 的时间。
处理步骤
先在本地用最小命令验证:docker login ghcr.io -u USERNAME,密码用 PAT。确认是超时、401、403,还是 TLS 报错。超时看网络和 DNS;401/403 看权限。
再检查 PAT 或 GITHUB_TOKEN。PAT 至少要和你的动作匹配:读私有包需要读权限,发布需要写权限。GitHub Actions 里别默认权限,显式写:packages: write 和必要的 contents: read。
接着区分 registry 地址。GitHub Container Registry 是 ghcr.io,不要和旧的 Docker package endpoint 混用。镜像名也要带 owner,例如 ghcr.io/org/app:tag。
最后看 CI runner。自托管 runner、公司网络、远程办公室和云厂商出口都可能对 registry 连接不稳定。GitHub-hosted runner 通常少一些环境差异,但也要看依赖缓存和镜像层大小。
CI 里怎么写更稳?
| 项目 | 建议 |
|---|---|
| 登录 | 用 docker/login-action 或明确 docker login |
| Token | 同仓库优先 GITHUB_TOKEN |
| 权限 | workflow 顶部声明 packages: write |
| 镜像名 | 固定 ghcr.io/<owner>/<name> |
| tag | 同时写 sha 和 semver |
| 超时 | 不在失败时无限重试 |
我还会把 build 和 push 分开打日志。很多 workflow 一行里做完 login、build、push,失败时只剩一个红叉。拆开后,你才知道是 registry 登录、构建缓存,还是上传镜像层的问题。
远程团队为什么更容易遇到
独立开发者常见的环境是:本地在一个地区,CI 在 GitHub,部署在另一家云,队友又在旅行。任何一段连 ghcr.io 慢,都会被误判成 GitHub Packages 坏了。实际上更常见的是 DNS、TLS、凭据过期、组织权限和镜像可见性。
另一个坑是权限继承。私有仓库生成的包,不一定自动对所有部署环境可读。组织迁移、仓库改名、包从 private 改 internal,都可能让旧部署 token 失效。
GitHub Container Registry login 继续排查清单
GITHUB_TOKEN是否有packages: write- PAT 是否过期,是否只有读权限却在 push
- 镜像是否发布到正确 owner
- package 是否关联到仓库
- 部署平台是否能访问
ghcr.io - 自托管 runner 的 Docker credential helper 是否损坏
- 是否把 push 慢误判成 login 慢
我还会检查 .docker/config.json。本地换过 Docker Desktop、credential helper 或登录多个 registry 后,这个文件里可能留着旧凭据。CI 里则要避免把本地配置复制进镜像构建上下文。凭据问题有时不是没有权限,而是拿错了旧权限。
另一个容易忽略的是镜像标签。latest 方便,但排查很痛苦。生产部署最好同时使用 git sha tag,失败时能回到确定版本。否则你以为是登录超时,实际上是部署机拉到了刚被覆盖的新镜像,行为和日志都对不上。
如果团队里有人在本地手动 push 镜像,我会尽快收口到 CI。手动 push 的问题不是技术上不能用,而是没有审计线索。CI 至少能留下 commit、runner、token、镜像 digest 和失败日志。等到生产拉取失败时,这些信息比任何口头描述都可靠。
最后看一次 registry 配额和并发。小团队常把前端、worker、后台任务都塞进同一个镜像发布流程,一次合并触发多条 push。并发多了以后,日志里看着像登录慢,实际是上传层排队。把无关服务拆成独立 workflow,失败面会小很多,也更容易回滚即可。
还没恢复时的排查
把镜像发布改到 GitHub-hosted runner 跑一遍,排除自托管 runner 环境问题;再把部署拉取改成只读 token。远程团队如果经常处理 GitHub Actions、Cloudflare、容器 registry 后台,可以准备海外服务跑 GitHub Actions / Cloudflare 的稳定线路,至少让排查环境固定下来。