GitHub Actions 部署拉取 ghcr.io 镜像失败的坑,我靠南京大学代理搞定了
做个人项目部署的时候,最烦的不是代码写不出来,而是 CI/CD 跑了一半,卡在拉镜像这一步。
最近我在给「极速PDF转换器」配 GitHub Actions 自动部署流程,后端用 Node,前端用 Vue,数据库是 Postgres,缓存用 Redis,还搭了一个 Umami 做数据统计。整个流程本来很顺:本地 push 代码 → GitHub Actions 触发 → SSH 到阿里云服务器 → docker compose up -d --build。但每次都在拉 ghcr.io/umami-software/umami:3.4 的时候卡死,报错 unexpected EOF,重试几次还是断。
问题定位
一开始以为是 Docker Hub 镜像加速器没配好,毕竟国内服务器直连 docker.io 本来就慢。我在服务器上配了 DaoCloud 的 registry-mirrors:
{
"registry-mirrors": [
"https://docker.m.daocloud.io"
]
}
配完重启 Docker,docker pull redis:7-alpine 秒拉成功,docker pull postgres 也没问题。但一拉 ghcr.io/umami-software/umami:3.4,还是老样子——下载进度条走到一半就断了。
查了一圈才发现:registry-mirrors 只对 docker.io 生效,ghcr.io 根本不走这个加速器。ghcr.io 是 GitHub Container Registry,和 Docker Hub 是两个完全不同的仓库,国内直连 GitHub 的 CDN 本身就不稳定,大镜像层下载到一半被掐断是常态。
踩过的坑
在找到最终方案之前,我试了好几种思路:
-
换不同的 Docker Hub 镜像源
换了docker.1ms.run、huecker.io、dockerhub.timeweb.cloud等等,全部无效——原因同上,这些只加速 docker.io。 -
给 ghcr.io 单独配镜像加速
尝试在daemon.json里加registry-config字段给 ghcr.io 指定代理,但 Docker 版本不支持这个配置,折腾半天没生效。 -
GitHub Actions 里加重试逻辑
在 workflow 里写了 bash 循环重试,最多重试 5 次,每次间隔 30 秒。结果是:重试 5 次,失败 5 次。网络层的问题,重试解决不了。 -
考虑上 Dokploy / Coolify
这类自托管 PaaS 确实能简化部署流程,但它们底层还是调docker compose pull,不解决 ghcr.io 拉取问题,换了个壳而已。
最终方案:南京大学 ghcr 代理
偶然在 GitHub 上看到一个国内高校维护的 ghcr 代理列表,试了南京大学的,居然一次成功:
docker pull ghcr.nju.edu.cn/umami-software/umami:3.4
52MB 的大层几秒钟就下完了,完全没有断流。拉取成功后,打回原名 tag:
docker tag ghcr.nju.edu.cn/umami-software/umami:3.4 ghcr.io/umami-software/umami:3.4
这样 docker-compose.yml 里写的 image: ghcr.io/umami-software/umami:3.4 完全不用改,Docker 启动时会直接命中本地缓存,不再去远程拉取。
集成到 GitHub Actions
验证成功后,我把这套流程写进了 workflow,拆成两步:
- name: Pull images via NJU proxy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/codeblank/deploy
docker pull ghcr.nju.edu.cn/umami-software/umami:3.4
docker tag ghcr.nju.edu.cn/umami-software/umami:3.4 ghcr.io/umami-software/umami:3.4
docker compose pull --quiet
- name: Build and deploy
uses: appleboy/ssh-action@v1.0.3
if: success()
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/codeblank/deploy
docker compose up -d --build
第一步只负责拉镜像(用 NJU 代理 + tag 回原名),第二步才构建部署。if: success() 保证了只有镜像拉成功了才会执行部署,避免半残服务启动。
小结
国内服务器拉 ghcr.io 镜像,registry-mirrors 帮不上忙,必须用 ghcr 专用代理。南京大学这个目前稳定可用,如果你也遇到类似问题,可以直接替换域名试试。
另外提醒一句:这类公益代理的可用性取决于维护方,建议部署前先在服务器上手动验证一下。如果后续不可用了,可以关注国内其他高校或社区维护的 ghcr 镜像列表。
浙公网安备 33010602011771号