我以为只是 pull 一下,结果整个架构被重写了
自建 Docker 镜像加速服务这件事,我跑了一年多。服务器放海外,专治一个老毛病 ——docker pull 官方镜像动不动就卡死、超时、EOF。
前阵子我想着,这服务也该例行更新一下了。心里想的就是一行命令的事:pull 一下、up 一下,完事。
结果一翻上游仓库,我愣住了。这哪是更新,这是一场迁移。
本来只想 pull,结果发现不对劲
先看版本。上游 dqzboy/Docker-Proxy最新 release 是 v5.1.4,8 月 13 号发的。变更列表不长:加了个一键改密码脚本、修了登录页 session 状态、给 WebSocket 加了鉴权。
看着挺温和,对吧。但翻 commit 历史才发现,7 月 21 号上游干了一件大事 —— 把后端整个重写了。
旧架构是 distribution registry,镜像拉下来会落盘缓存。新架构直接换成了纯 Go 实现,零磁盘缓存,按 Host 自动路由到 Docker Hub、GHCR、Quay、K8s 这些仓库。服务端做完 token 鉴权就流式转发,不落盘、不占本地存储。
架构一换,compose 文件跟着大改。我把服务器上的旧 compose 和上游新 compose 摆在一起:
| 项目 | 旧架构 | 新架构 |
|---|---|---|
| 服务数 | 3 个 | 2 个 |
| 后端 | dockerhub(distribution registry,磁盘缓存) | go-proxy(Go 零缓存) |
| 管理面板 | registry-ui | 已删除,功能并入 hubcmd-ui |
| 后端端口 | 50001 | 50000 |
| UI 端口 | 50003 | 30080 |
| 后端配置 | registry-hub.yml | config.yaml(自动播种) |
| UI 数据目录 | hubcmd-data | data/hubcmd-ui |
看到第三行我就警觉了:registry-ui 这个组件被上游直接删了。
更麻烦的是下面这个坑 —— 我服务器上的 hubcmd-ui:latest 早就是新版了,但这个新版要一个环境变量 GO_PROXY_ADMIN_TOKEN 才能连后端,而我的旧 compose 里根本没这个变量。
换句话说,如果我真按原计划直接 docker compose pull && up -d,面板会拉起来,但连不上后端,白屏。
这就是 :latest 的代价 —— 标签没变,镜像已经换代了。
迁移过程
既然躲不掉,那就正经迁。思路清晰,四步走。
第一步,备份。compose、.env、config、数据目录全拷一份。唯独那个 2.3G 的 registry 缓存目录不用动 —— 新架构零缓存,它本来就不参与。
cd /path/to/docker-proxycp -a docker-compose.yaml .env config hubcmd-data docker-proxy.bak.$(date +%F)/第二步,重写 compose。这里我做了个关键决定:端口保持不变。go-proxy 继续用 50001,hubcmd-ui 继续用 50003,这样外面配的反代、隧道统统不用动。
services: go-proxy: image: ${REGISTRY_IMAGE:-dqzboy/registry:latest} container_name: reg-go-proxy restart: always environment: - OTEL_TRACES_EXPORTER=none - GO_PROXY_ADMIN_TOKEN=${GO_PROXY_ADMIN_TOKEN:-change-me-to-a-strong-random-token} volumes: - ./config/go-proxy:/app/config.d ports: - 50001:5000 networks: - registry-net
21 collapsed lines
hubcmd-ui: image: ${UI_IMAGE:-dqzboy/hubcmd-ui:latest} container_name: hubcmd-ui restart: always depends_on: - go-proxy environment: - GO_PROXY_ADMIN_URL=http://go-proxy:5001 - GO_PROXY_ADMIN_TOKEN=${GO_PROXY_ADMIN_TOKEN:-change-me-to-a-strong-random-token} - HOST_NAME=${HOST_NAME:-} - SECURE_COOKIE=false volumes: - /var/run/docker.sock:/var/run/docker.sock - ./data/hubcmd-ui:/app/data ports: - 50003:3000 networks: - registry-net
networks: registry-net:第三步,生成 .env。新架构要两样东西:一个随机 admin token、主机名。
cat > .env <<EOFGO_PROXY_ADMIN_TOKEN=$(openssl rand -hex 24)HOST_NAME=$(hostname)REGISTRY_IMAGE=dqzboy/registry:latestUI_IMAGE=dqzboy/hubcmd-ui:latestEOF第四步,迁移数据目录,然后启动。
mkdir -p datacp -a hubcmd-data data/hubcmd-ui # 旧数据 copy 过去,原目录留着当后路
docker compose config --quiet && echo OKdocker compose pulldocker compose up -d起来之后,三个验证全过才算成功。
# 1. 拉镜像端点活着curl -s -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:50001/v2/
# 2. 管理面板活着curl -s -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:50003/
# 3. 真拉一个镜像,确认走的是新代理docker pull 127.0.0.1:50001/library/busybox:latestbusybox 拉下来的那一刻,心里踏实了。真的走的是新代理,不是本地缓存。
踩坑记录
记几个这次撞上的坑,下次能少走弯路。
:latest 是个温柔的陷阱。 你以为 pull 一下就是最新,其实它可能已经跨过了一次架构换代,配置早就对不上了。真要稳,pin 个 tag 比什么都强。
别死守被上游删掉的组件。 registry-ui 这个组件没了就是没了,别想着「我再单独拉一个跑起来」—— 那是在跟上游对着干。
旧磁盘缓存别心疼。 2.3G 的 registry 缓存在零缓存架构下直接作废,删了就删了。反正源头在 Docker Hub,随时能再拉。
迁移之后,拉取又失败了
迁移完,我把域名配上去,指到 go-proxy。
先用浏览器打开,404。第一反应是「坏了,没配好」。
其实不是。go-proxy 是拉镜像的 API,不是网页,根路径本来就什么都没有。浏览器 404 是正常现象 —— 真正该测的是 /v2/ 端点和 docker pull。
结果一测,pull 还真拉不动,manifest 接口返回 502。
查日志,go-proxy 报的是:
token endpoint returned 401: {"details":"incorrect username or password"}原来我在面板里给 dockerhub 段填了 Docker Hub 的账号密码,本想着提一下匿名拉取的频率限制。但 Docker Hub 现在的 token 端点只认 Personal Access Token(PAT),密码直接填进去就是 401。go-proxy 拿不到 token,就把 502 抛给了上游。
解决办法简单粗暴:把 username / password 清空,走匿名。公开镜像的拉取,匿名完全够用。
清空、重启,再拉一遍 ——alpine 下来了,域名也通了。
「网页 404」和「拉不了镜像」,一个是正常现象,一个是真 bug。前者别慌,后者查日志。
总结
这次更新教会我一件事:自托管服务里的 :latest,等于把「要不要迁移」的决定权,交到了上游手里。
下次再看到 compose 变了,别急着 pull。先看看 commit 里,是不是藏着一场重写。

